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 greetingfolder[64];
129 static char authuser[32];
130 static char authpassword[42];
132 static int expungeonhangup = 1;
133 static int imapgreetings = 0;
134 static char delimiter = '\0';
139 /* Forward declarations for IMAP */
140 static int init_mailstream(struct vm_state *vms, int box);
141 static void write_file(char *filename, char *buffer, unsigned long len);
142 static char *get_header_by_tag(char *header, char *tag, char *buf, size_t len);
143 static void vm_imap_delete(int msgnum, struct vm_state *vms);
144 static char *get_user_by_mailbox(char *mailbox, char *buf, size_t len);
145 static struct vm_state *get_vm_state_by_imapuser(char *user, int interactive);
146 static struct vm_state *get_vm_state_by_mailbox(const char *mailbox, int interactive);
147 static void vmstate_insert(struct vm_state *vms);
148 static void vmstate_delete(struct vm_state *vms);
149 static void set_update(MAILSTREAM * stream);
150 static void init_vm_state(struct vm_state *vms);
151 static void check_msgArray(struct vm_state *vms);
152 static void copy_msgArray(struct vm_state *dst, struct vm_state *src);
153 static int save_body(BODY *body, struct vm_state *vms, char *section, char *format, char *altfile);
154 static int make_gsm_file(char *dest, size_t len, char *imapuser, char *dir, int num, char *prefix);
155 static void get_mailbox_delimiter(MAILSTREAM *stream);
156 static void mm_parsequota (MAILSTREAM *stream, unsigned char *msg, QUOTALIST *pquota);
157 static void imap_mailbox_name(char *spec, size_t len, struct vm_state *vms, int box, int target);
158 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, char *introfile, const char *flag);
159 static void update_messages_by_imapuser(const char *user, unsigned long number);
161 static int imap_remove_file (char *dir, int msgnum);
162 static int imap_retrieve_file (char *dir, int msgnum, const char *mailbox, const char *context);
163 static int imap_delete_old_greeting (char *dir, struct vm_state *vms);
164 static void check_quota(struct vm_state *vms, char *mailbox);
165 static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu, int box);
167 struct vm_state *vms;
168 AST_LIST_ENTRY(vmstate) list;
171 static AST_LIST_HEAD_STATIC(vmstates, vmstate);
175 #define SMDI_MWI_WAIT_TIMEOUT 1000 /* 1 second */
177 #define COMMAND_TIMEOUT 5000
178 /* Don't modify these here; set your umask at runtime instead */
179 #define VOICEMAIL_DIR_MODE 0777
180 #define VOICEMAIL_FILE_MODE 0666
181 #define CHUNKSIZE 65536
183 #define VOICEMAIL_CONFIG "voicemail.conf"
184 #define ASTERISK_USERNAME "asterisk"
186 /* Define fast-forward, pause, restart, and reverse keys
187 while listening to a voicemail message - these are
188 strings, not characters */
189 #define DEFAULT_LISTEN_CONTROL_FORWARD_KEY "#"
190 #define DEFAULT_LISTEN_CONTROL_REVERSE_KEY "*"
191 #define DEFAULT_LISTEN_CONTROL_PAUSE_KEY "0"
192 #define DEFAULT_LISTEN_CONTROL_RESTART_KEY "2"
193 #define DEFAULT_LISTEN_CONTROL_STOP_KEY "13456789"
194 #define VALID_DTMF "1234567890*#" /* Yes ABCD are valid dtmf but what phones have those? */
196 /* Default mail command to mail voicemail. Change it with the
197 mailcmd= command in voicemail.conf */
198 #define SENDMAIL "/usr/sbin/sendmail -t"
200 #define INTRO "vm-intro"
203 #define MAXMSGLIMIT 9999
205 #define MINPASSWORD 0 /*!< Default minimum mailbox password length */
207 #define BASELINELEN 72
208 #define BASEMAXINLINE 256
211 #define MAX_DATETIME_FORMAT 512
212 #define MAX_NUM_CID_CONTEXTS 10
214 #define VM_REVIEW (1 << 0) /*!< After recording, permit the caller to review the recording before saving */
215 #define VM_OPERATOR (1 << 1) /*!< Allow 0 to be pressed to go to 'o' extension */
216 #define VM_SAYCID (1 << 2) /*!< Repeat the CallerID info during envelope playback */
217 #define VM_SVMAIL (1 << 3) /*!< Allow the user to compose a new VM from within VoicemailMain */
218 #define VM_ENVELOPE (1 << 4) /*!< Play the envelope information (who-from, time received, etc.) */
219 #define VM_SAYDURATION (1 << 5) /*!< Play the length of the message during envelope playback */
220 #define VM_SKIPAFTERCMD (1 << 6) /*!< After deletion, assume caller wants to go to the next message */
221 #define VM_FORCENAME (1 << 7) /*!< Have new users record their name */
222 #define VM_FORCEGREET (1 << 8) /*!< Have new users record their greetings */
223 #define VM_PBXSKIP (1 << 9) /*!< Skip the [PBX] preamble in the Subject line of emails */
224 #define VM_DIRECFORWARD (1 << 10) /*!< Permit caller to use the Directory app for selecting to which mailbox to forward a VM */
225 #define VM_ATTACH (1 << 11) /*!< Attach message to voicemail notifications? */
226 #define VM_DELETE (1 << 12) /*!< Delete message after sending notification */
227 #define VM_ALLOCED (1 << 13) /*!< Structure was malloc'ed, instead of placed in a return (usually static) buffer */
228 #define VM_SEARCH (1 << 14) /*!< Search all contexts for a matching mailbox */
229 #define VM_TEMPGREETWARN (1 << 15) /*!< Remind user tempgreeting is set */
230 #define VM_MOVEHEARD (1 << 16) /*!< Move a "heard" message to Old after listening to it */
231 #define VM_MESSAGEWRAP (1 << 17) /*!< Wrap around from the last message to the first, and vice-versa */
232 #define ERROR_LOCK_PATH -100
245 OPT_SILENT = (1 << 0),
246 OPT_BUSY_GREETING = (1 << 1),
247 OPT_UNAVAIL_GREETING = (1 << 2),
248 OPT_RECORDGAIN = (1 << 3),
249 OPT_PREPEND_MAILBOX = (1 << 4),
250 OPT_AUTOPLAY = (1 << 6),
251 OPT_DTMFEXIT = (1 << 7),
252 OPT_MESSAGE_Urgent = (1 << 8),
253 OPT_MESSAGE_PRIORITY = (1 << 9)
257 OPT_ARG_RECORDGAIN = 0,
258 OPT_ARG_PLAYFOLDER = 1,
259 OPT_ARG_DTMFEXIT = 2,
260 /* This *must* be the last value in this enum! */
261 OPT_ARG_ARRAY_SIZE = 3,
264 AST_APP_OPTIONS(vm_app_options, {
265 AST_APP_OPTION('s', OPT_SILENT),
266 AST_APP_OPTION('b', OPT_BUSY_GREETING),
267 AST_APP_OPTION('u', OPT_UNAVAIL_GREETING),
268 AST_APP_OPTION_ARG('g', OPT_RECORDGAIN, OPT_ARG_RECORDGAIN),
269 AST_APP_OPTION_ARG('d', OPT_DTMFEXIT, OPT_ARG_DTMFEXIT),
270 AST_APP_OPTION('p', OPT_PREPEND_MAILBOX),
271 AST_APP_OPTION_ARG('a', OPT_AUTOPLAY, OPT_ARG_PLAYFOLDER),
272 AST_APP_OPTION('U', OPT_MESSAGE_Urgent),
273 AST_APP_OPTION('P', OPT_MESSAGE_PRIORITY)
276 static int load_config(int reload);
278 /*! \page vmlang Voicemail Language Syntaxes Supported
280 \par Syntaxes supported, not really language codes.
287 \arg \b pt - Portuguese
288 \arg \b pt_BR - Portuguese (Brazil)
290 \arg \b no - Norwegian
292 \arg \b tw - Chinese (Taiwan)
293 \arg \b ua - Ukrainian
295 German requires the following additional soundfile:
296 \arg \b 1F einE (feminine)
298 Spanish requires the following additional soundfile:
299 \arg \b 1M un (masculine)
301 Dutch, Portuguese & Spanish require the following additional soundfiles:
302 \arg \b vm-INBOXs singular of 'new'
303 \arg \b vm-Olds singular of 'old/heard/read'
306 \arg \b vm-INBOX nieuwe (nl)
307 \arg \b vm-Old oude (nl)
310 \arg \b vm-new-a 'new', feminine singular accusative
311 \arg \b vm-new-e 'new', feminine plural accusative
312 \arg \b vm-new-ych 'new', feminine plural genitive
313 \arg \b vm-old-a 'old', feminine singular accusative
314 \arg \b vm-old-e 'old', feminine plural accusative
315 \arg \b vm-old-ych 'old', feminine plural genitive
316 \arg \b digits/1-a 'one', not always same as 'digits/1'
317 \arg \b digits/2-ie 'two', not always same as 'digits/2'
320 \arg \b vm-nytt singular of 'new'
321 \arg \b vm-nya plural of 'new'
322 \arg \b vm-gammalt singular of 'old'
323 \arg \b vm-gamla plural of 'old'
324 \arg \b digits/ett 'one', not always same as 'digits/1'
327 \arg \b vm-ny singular of 'new'
328 \arg \b vm-nye plural of 'new'
329 \arg \b vm-gammel singular of 'old'
330 \arg \b vm-gamle plural of 'old'
338 Ukrainian requires the following additional soundfile:
339 \arg \b vm-nove 'nove'
340 \arg \b vm-stare 'stare'
341 \arg \b digits/ua/1e 'odne'
343 Italian requires the following additional soundfile:
347 \arg \b vm-nuovi new plural
348 \arg \b vm-vecchio old
349 \arg \b vm-vecchi old plural
351 Chinese (Taiwan) requires the following additional soundfile:
352 \arg \b vm-tong A class-word for call (tong1)
353 \arg \b vm-ri A class-word for day (ri4)
354 \arg \b vm-you You (ni3)
355 \arg \b vm-haveno Have no (mei2 you3)
356 \arg \b vm-have Have (you3)
357 \arg \b vm-listen To listen (yao4 ting1)
360 \note Don't use vm-INBOX or vm-Old, because they are the name of the INBOX and Old folders,
361 spelled among others when you have to change folder. For the above reasons, vm-INBOX
362 and vm-Old are spelled plural, to make them sound more as folder name than an adjective.
371 unsigned char iobuf[BASEMAXINLINE];
374 /*! Structure for linked list of users
375 * Use ast_vm_user_destroy() to free one of these structures. */
377 char context[AST_MAX_CONTEXT]; /*!< Voicemail context */
378 char mailbox[AST_MAX_EXTENSION]; /*!< Mailbox id, unique within vm context */
379 char password[80]; /*!< Secret pin code, numbers only */
380 char fullname[80]; /*!< Full name, for directory app */
381 char email[80]; /*!< E-mail address */
382 char pager[80]; /*!< E-mail address to pager (no attachment) */
383 char serveremail[80]; /*!< From: Mail address */
384 char mailcmd[160]; /*!< Configurable mail command */
385 char language[MAX_LANGUAGE]; /*!< Config: Language setting */
386 char zonetag[80]; /*!< Time zone */
389 char uniqueid[80]; /*!< Unique integer identifier */
391 char attachfmt[20]; /*!< Attachment format */
392 unsigned int flags; /*!< VM_ flags */
394 int maxmsg; /*!< Maximum number of msgs per folder for this mailbox */
395 int maxdeletedmsg; /*!< Maximum number of deleted msgs saved for this mailbox */
396 int maxsecs; /*!< Maximum number of seconds per message for this mailbox */
398 char imapuser[80]; /*!< IMAP server login */
399 char imappassword[80]; /*!< IMAP server password if authpassword not defined */
401 double volgain; /*!< Volume gain for voicemails sent via email */
402 AST_LIST_ENTRY(ast_vm_user) list;
405 /*! Voicemail time zones */
407 AST_LIST_ENTRY(vm_zone) list;
410 char msg_format[512];
413 #define VMSTATE_MAX_MSG_ARRAY 256
415 /*! Voicemail mailbox state */
419 char curdir[PATH_MAX];
420 char vmbox[PATH_MAX];
423 char intro[PATH_MAX];
435 int updated; /*!< decremented on each mail check until 1 -allows delay */
436 long msgArray[VMSTATE_MAX_MSG_ARRAY];
437 MAILSTREAM *mailstream;
439 char imapuser[80]; /*!< IMAP server login */
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,k) 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) (delete_file(a,b))
459 #define RETRIEVE(a,b,c,d) (imap_retrieve_file(a,b,c,d ))
460 #define DISPOSE(a,b) (imap_remove_file(a,b))
461 #define STORE(a,b,c,d,e,f,g,h,i,j,k) (imap_store_file(a,b,c,d,e,f,g,h,i,j,k))
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 IMAP_DELETE(a,b,c,d) (vm_imap_delete(b,d))
466 #define DELETE(a,b,c) (vm_delete(c))
468 #define RETRIEVE(a,b,c,d)
470 #define STORE(a,b,c,d,e,f,g,h,i,j,k)
471 #define EXISTS(a,b,c,d) (ast_fileexists(c,NULL,d) > 0)
472 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(g,h));
473 #define COPY(a,b,c,d,e,f,g,h) (copy_plain_file(g,h));
474 #define DELETE(a,b,c) (vm_delete(c))
478 static char VM_SPOOL_DIR[PATH_MAX];
480 static char ext_pass_cmd[128];
481 static char ext_pass_check_cmd[128];
485 #define PWDCHANGE_INTERNAL (1 << 1)
486 #define PWDCHANGE_EXTERNAL (1 << 2)
487 static int pwdchange = PWDCHANGE_INTERNAL;
490 #define tdesc "Comedian Mail (Voicemail System) with ODBC Storage"
493 # define tdesc "Comedian Mail (Voicemail System) with IMAP Storage"
495 # define tdesc "Comedian Mail (Voicemail System)"
499 static char userscontext[AST_MAX_EXTENSION] = "default";
501 static char *addesc = "Comedian Mail";
503 static char *synopsis_vm = "Leave a Voicemail message";
505 static char *descrip_vm =
506 " VoiceMail(mailbox[@context][&mailbox[@context]][...][,options]): This\n"
507 "application allows the calling party to leave a message for the specified\n"
508 "list of mailboxes. When multiple mailboxes are specified, the greeting will\n"
509 "be taken from the first mailbox specified. Dialplan execution will stop if the\n"
510 "specified mailbox does not exist.\n"
511 " The Voicemail application will exit if any of the following DTMF digits are\n"
513 " 0 - Jump to the 'o' extension in the current dialplan context.\n"
514 " * - Jump to the 'a' extension in the current dialplan context.\n"
515 " This application will set the following channel variable upon completion:\n"
516 " VMSTATUS - This indicates the status of the execution of the VoiceMail\n"
517 " application. The possible values are:\n"
518 " SUCCESS | USEREXIT | FAILED\n\n"
520 " b - Play the 'busy' greeting to the calling party.\n"
521 " d([c]) - Accept digits for a new extension in context c, if played during\n"
522 " the greeting. Context defaults to the current context.\n"
523 " g(#) - Use the specified amount of gain when recording the voicemail\n"
524 " message. The units are whole-number decibels (dB).\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);
706 * \brief Sets default voicemail system options to a voicemail user.
708 * This applies select global settings to a newly created (dynamic) instance of a voicemail user.
709 * - all the globalflags
710 * - the saydurationminfo
714 * - vmmaxsecs, vmmaxmsg, maxdeletedmsg
717 static void populate_defaults(struct ast_vm_user *vmu)
719 ast_copy_flags(vmu, (&globalflags), AST_FLAGS_ALL);
720 if (saydurationminfo)
721 vmu->saydurationm = saydurationminfo;
722 ast_copy_string(vmu->callback, callcontext, sizeof(vmu->callback));
723 ast_copy_string(vmu->dialout, dialcontext, sizeof(vmu->dialout));
724 ast_copy_string(vmu->exit, exitcontext, sizeof(vmu->exit));
726 vmu->maxsecs = vmmaxsecs;
728 vmu->maxmsg = maxmsg;
730 vmu->maxdeletedmsg = maxdeletedmsg;
731 vmu->volgain = volgain;
735 * \brief Sets a a specific property value.
736 * \param vmu The voicemail user object to work with.
737 * \param var The name of the property to be set.
738 * \param value The value to be set to the property.
740 * The property name must be one of the understood properties. See the source for details.
742 static void apply_option(struct ast_vm_user *vmu, const char *var, const char *value)
745 if (!strcasecmp(var, "attach")) {
746 ast_set2_flag(vmu, ast_true(value), VM_ATTACH);
747 } else if (!strcasecmp(var, "attachfmt")) {
748 ast_copy_string(vmu->attachfmt, value, sizeof(vmu->attachfmt));
749 } else if (!strcasecmp(var, "serveremail")) {
750 ast_copy_string(vmu->serveremail, value, sizeof(vmu->serveremail));
751 } else if (!strcasecmp(var, "language")) {
752 ast_copy_string(vmu->language, value, sizeof(vmu->language));
753 } else if (!strcasecmp(var, "tz")) {
754 ast_copy_string(vmu->zonetag, value, sizeof(vmu->zonetag));
756 } else if (!strcasecmp(var, "imapuser")) {
757 ast_copy_string(vmu->imapuser, value, sizeof(vmu->imapuser));
758 } else if (!strcasecmp(var, "imappassword")) {
759 ast_copy_string(vmu->imappassword, value, sizeof(vmu->imappassword));
761 } else if (!strcasecmp(var, "delete") || !strcasecmp(var, "deletevoicemail")) {
762 ast_set2_flag(vmu, ast_true(value), VM_DELETE);
763 } else if (!strcasecmp(var, "saycid")){
764 ast_set2_flag(vmu, ast_true(value), VM_SAYCID);
765 } else if (!strcasecmp(var,"sendvoicemail")){
766 ast_set2_flag(vmu, ast_true(value), VM_SVMAIL);
767 } else if (!strcasecmp(var, "review")){
768 ast_set2_flag(vmu, ast_true(value), VM_REVIEW);
769 } else if (!strcasecmp(var, "tempgreetwarn")){
770 ast_set2_flag(vmu, ast_true(value), VM_TEMPGREETWARN);
771 } else if (!strcasecmp(var, "messagewrap")){
772 ast_set2_flag(vmu, ast_true(value), VM_MESSAGEWRAP);
773 } else if (!strcasecmp(var, "operator")) {
774 ast_set2_flag(vmu, ast_true(value), VM_OPERATOR);
775 } else if (!strcasecmp(var, "envelope")){
776 ast_set2_flag(vmu, ast_true(value), VM_ENVELOPE);
777 } else if (!strcasecmp(var, "moveheard")){
778 ast_set2_flag(vmu, ast_true(value), VM_MOVEHEARD);
779 } else if (!strcasecmp(var, "sayduration")){
780 ast_set2_flag(vmu, ast_true(value), VM_SAYDURATION);
781 } else if (!strcasecmp(var, "saydurationm")){
782 if (sscanf(value, "%d", &x) == 1) {
783 vmu->saydurationm = x;
785 ast_log(AST_LOG_WARNING, "Invalid min duration for say duration\n");
787 } else if (!strcasecmp(var, "forcename")){
788 ast_set2_flag(vmu, ast_true(value), VM_FORCENAME);
789 } else if (!strcasecmp(var, "forcegreetings")){
790 ast_set2_flag(vmu, ast_true(value), VM_FORCEGREET);
791 } else if (!strcasecmp(var, "callback")) {
792 ast_copy_string(vmu->callback, value, sizeof(vmu->callback));
793 } else if (!strcasecmp(var, "dialout")) {
794 ast_copy_string(vmu->dialout, value, sizeof(vmu->dialout));
795 } else if (!strcasecmp(var, "exitcontext")) {
796 ast_copy_string(vmu->exit, value, sizeof(vmu->exit));
797 } else if (!strcasecmp(var, "maxmessage") || !strcasecmp(var, "maxsecs")) {
798 if (vmu->maxsecs <= 0) {
799 ast_log(AST_LOG_WARNING, "Invalid max message length of %s. Using global value %d\n", value, vmmaxsecs);
800 vmu->maxsecs = vmmaxsecs;
802 vmu->maxsecs = atoi(value);
804 if (!strcasecmp(var, "maxmessage"))
805 ast_log(AST_LOG_WARNING, "Option 'maxmessage' has been deprecated in favor of 'maxsecs'. Please make that change in your voicemail config.\n");
806 } else if (!strcasecmp(var, "maxmsg")) {
807 vmu->maxmsg = atoi(value);
808 if (vmu->maxmsg <= 0) {
809 ast_log(AST_LOG_WARNING, "Invalid number of messages per folder maxmsg=%s. Using default value %d\n", value, MAXMSG);
810 vmu->maxmsg = MAXMSG;
811 } else if (vmu->maxmsg > MAXMSGLIMIT) {
812 ast_log(AST_LOG_WARNING, "Maximum number of messages per folder is %d. Cannot accept value maxmsg=%s\n", MAXMSGLIMIT, value);
813 vmu->maxmsg = MAXMSGLIMIT;
815 } else if (!strcasecmp(var, "backupdeleted")) {
816 if (sscanf(value, "%d", &x) == 1)
817 vmu->maxdeletedmsg = x;
818 else if (ast_true(value))
819 vmu->maxdeletedmsg = MAXMSG;
821 vmu->maxdeletedmsg = 0;
823 if (vmu->maxdeletedmsg < 0) {
824 ast_log(AST_LOG_WARNING, "Invalid number of deleted messages saved per mailbox backupdeleted=%s. Using default value %d\n", value, MAXMSG);
825 vmu->maxdeletedmsg = MAXMSG;
826 } else if (vmu->maxdeletedmsg > MAXMSGLIMIT) {
827 ast_log(AST_LOG_WARNING, "Maximum number of deleted messages saved per mailbox is %d. Cannot accept value backupdeleted=%s\n", MAXMSGLIMIT, value);
828 vmu->maxdeletedmsg = MAXMSGLIMIT;
830 } else if (!strcasecmp(var, "volgain")) {
831 sscanf(value, "%lf", &vmu->volgain);
832 } else if (!strcasecmp(var, "options")) {
833 apply_options(vmu, value);
837 static char *vm_check_password_shell(char *command, char *buf, size_t len)
844 snprintf(buf, len, "FAILURE: Pipe failed: %s", strerror(errno));
847 pid = ast_safe_fork(0);
853 snprintf(buf, len, "FAILURE: Fork failed");
857 read(fds[0], buf, len);
861 AST_DECLARE_APP_ARGS(arg,
864 char *mycmd = ast_strdupa(command);
867 dup2(fds[1], STDOUT_FILENO);
869 ast_close_fds_above_n(STDOUT_FILENO);
871 AST_NONSTANDARD_APP_ARGS(arg, mycmd, ' ');
873 execv(arg.v[0], arg.v);
874 printf("FAILURE: %s", strerror(errno));
882 * \brief Check that password meets minimum required length
883 * \param vmu The voicemail user to change the password for.
884 * \param password The password string to check
886 * \return zero on ok, 1 on not ok.
888 static int check_password(struct ast_vm_user *vmu, char *password)
890 /* check minimum length */
891 if (strlen(password) < minpassword)
893 if (!ast_strlen_zero(ext_pass_check_cmd)) {
894 char cmd[255], buf[255];
896 ast_log(LOG_DEBUG, "Verify password policies for %s\n", password);
898 snprintf(cmd, sizeof(cmd), "%s %s %s %s %s", ext_pass_check_cmd, vmu->mailbox, vmu->context, vmu->password, password);
899 if (vm_check_password_shell(cmd, buf, sizeof(buf))) {
900 ast_debug(5, "Result: %s\n", buf);
901 if (!strncasecmp(buf, "VALID", 5)) {
902 ast_debug(3, "Passed password check: '%s'\n", buf);
904 } else if (!strncasecmp(buf, "FAILURE", 7)) {
905 ast_log(LOG_WARNING, "Unable to execute password validation script: '%s'.\n", buf);
908 ast_log(LOG_NOTICE, "Password doesn't match policies for user %s %s\n", vmu->mailbox, password);
917 * \brief Performs a change of the voicemail passowrd in the realtime engine.
918 * \param vmu The voicemail user to change the password for.
919 * \param password The new value to be set to the password for this user.
921 * This only works if the voicemail user has a unique id, and if there is a realtime engine configured.
922 * This is called from the (top level) vm_change_password.
924 * \return zero on success, -1 on error.
926 static int change_password_realtime(struct ast_vm_user *vmu, const char *password)
929 if (!ast_strlen_zero(vmu->uniqueid)) {
930 res = ast_update_realtime("voicemail", "uniqueid", vmu->uniqueid, "password", password, NULL);
932 ast_copy_string(vmu->password, password, sizeof(vmu->password));
943 * \brief Destructively Parse options and apply.
945 static void apply_options(struct ast_vm_user *vmu, const char *options)
950 stringp = ast_strdupa(options);
951 while ((s = strsep(&stringp, "|"))) {
953 if ((var = strsep(&value, "=")) && value) {
954 apply_option(vmu, var, value);
960 * \brief Loads the options specific to a voicemail user.
962 * This is called when a vm_user structure is being set up, such as from load_options.
964 static void apply_options_full(struct ast_vm_user *retval, struct ast_variable *var)
966 struct ast_variable *tmp;
969 if (!strcasecmp(tmp->name, "vmsecret")) {
970 ast_copy_string(retval->password, tmp->value, sizeof(retval->password));
971 } else if (!strcasecmp(tmp->name, "secret") || !strcasecmp(tmp->name, "password")) { /* don't overwrite vmsecret if it exists */
972 if (ast_strlen_zero(retval->password))
973 ast_copy_string(retval->password, tmp->value, sizeof(retval->password));
974 } else if (!strcasecmp(tmp->name, "uniqueid")) {
975 ast_copy_string(retval->uniqueid, tmp->value, sizeof(retval->uniqueid));
976 } else if (!strcasecmp(tmp->name, "pager")) {
977 ast_copy_string(retval->pager, tmp->value, sizeof(retval->pager));
978 } else if (!strcasecmp(tmp->name, "email")) {
979 ast_copy_string(retval->email, tmp->value, sizeof(retval->email));
980 } else if (!strcasecmp(tmp->name, "fullname")) {
981 ast_copy_string(retval->fullname, tmp->value, sizeof(retval->fullname));
982 } else if (!strcasecmp(tmp->name, "context")) {
983 ast_copy_string(retval->context, tmp->value, sizeof(retval->context));
985 } else if (!strcasecmp(tmp->name, "imapuser")) {
986 ast_copy_string(retval->imapuser, tmp->value, sizeof(retval->imapuser));
987 } else if (!strcasecmp(tmp->name, "imappassword")) {
988 ast_copy_string(retval->imappassword, tmp->value, sizeof(retval->imappassword));
991 apply_option(retval, tmp->name, tmp->value);
997 * \brief Determines if a DTMF key entered is valid.
998 * \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.
1000 * Tests the character entered against the set of valid DTMF characters.
1001 * \return 1 if the character entered is a valid DTMF digit, 0 if the character is invalid.
1003 static int is_valid_dtmf(const char *key)
1006 char *local_key = ast_strdupa(key);
1008 for (i = 0; i < strlen(key); ++i) {
1009 if (!strchr(VALID_DTMF, *local_key)) {
1010 ast_log(AST_LOG_WARNING, "Invalid DTMF key \"%c\" used in voicemail configuration file\n", *local_key);
1019 * \brief Finds a voicemail user from the realtime engine.
1024 * 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.
1026 * \return The ast_vm_user structure for the user that was found.
1028 static struct ast_vm_user *find_user_realtime(struct ast_vm_user *ivm, const char *context, const char *mailbox)
1030 struct ast_variable *var;
1031 struct ast_vm_user *retval;
1033 if ((retval = (ivm ? ivm : ast_calloc(1, sizeof(*retval))))) {
1035 ast_set_flag(retval, VM_ALLOCED);
1037 memset(retval, 0, sizeof(*retval));
1039 ast_copy_string(retval->mailbox, mailbox, sizeof(retval->mailbox));
1040 populate_defaults(retval);
1041 if (!context && ast_test_flag((&globalflags), VM_SEARCH))
1042 var = ast_load_realtime("voicemail", "mailbox", mailbox, NULL);
1044 var = ast_load_realtime("voicemail", "mailbox", mailbox, "context", context, NULL);
1046 apply_options_full(retval, var);
1047 ast_variables_destroy(var);
1058 * \brief Finds a voicemail user from the users file or the realtime engine.
1063 * \return The ast_vm_user structure for the user that was found.
1065 static struct ast_vm_user *find_user(struct ast_vm_user *ivm, const char *context, const char *mailbox)
1067 /* This function could be made to generate one from a database, too */
1068 struct ast_vm_user *vmu=NULL, *cur;
1069 AST_LIST_LOCK(&users);
1071 if (!context && !ast_test_flag((&globalflags), VM_SEARCH))
1072 context = "default";
1074 AST_LIST_TRAVERSE(&users, cur, list) {
1075 if (ast_test_flag((&globalflags), VM_SEARCH) && !strcasecmp(mailbox, cur->mailbox))
1077 if (context && (!strcasecmp(context, cur->context)) && (!strcasecmp(mailbox, cur->mailbox)))
1081 /* Make a copy, so that on a reload, we have no race */
1082 if ((vmu = (ivm ? ivm : ast_malloc(sizeof(*vmu))))) {
1083 memcpy(vmu, cur, sizeof(*vmu));
1084 ast_set2_flag(vmu, !ivm, VM_ALLOCED);
1085 AST_LIST_NEXT(vmu, list) = NULL;
1088 vmu = find_user_realtime(ivm, context, mailbox);
1089 AST_LIST_UNLOCK(&users);
1094 * \brief Resets a user password to a specified password.
1099 * This does the actual change password work, called by the vm_change_password() function.
1101 * \return zero on success, -1 on error.
1103 static int reset_user_pw(const char *context, const char *mailbox, const char *newpass)
1105 /* This function could be made to generate one from a database, too */
1106 struct ast_vm_user *cur;
1108 AST_LIST_LOCK(&users);
1109 AST_LIST_TRAVERSE(&users, cur, list) {
1110 if ((!context || !strcasecmp(context, cur->context)) &&
1111 (!strcasecmp(mailbox, cur->mailbox)))
1115 ast_copy_string(cur->password, newpass, sizeof(cur->password));
1118 AST_LIST_UNLOCK(&users);
1123 * \brief The handler for the change password option.
1124 * \param vmu The voicemail user to work with.
1125 * \param newpassword The new password (that has been gathered from the appropriate prompting).
1126 * 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.
1127 * It is also called when the user wants to change their password from menu option '5' on the mailbox options menu.
1129 static void vm_change_password(struct ast_vm_user *vmu, const char *newpassword)
1131 struct ast_config *cfg=NULL;
1132 struct ast_variable *var=NULL;
1133 struct ast_category *cat=NULL;
1134 char *category=NULL, *value=NULL, *new=NULL;
1135 const char *tmp=NULL;
1136 struct ast_flags config_flags = { CONFIG_FLAG_WITHCOMMENTS };
1137 if (!change_password_realtime(vmu, newpassword))
1140 /* check voicemail.conf */
1141 if ((cfg = ast_config_load(VOICEMAIL_CONFIG, config_flags))) {
1142 while ((category = ast_category_browse(cfg, category))) {
1143 if (!strcasecmp(category, vmu->context)) {
1144 if (!(tmp = ast_variable_retrieve(cfg, category, vmu->mailbox))) {
1145 ast_log(AST_LOG_WARNING, "We could not find the mailbox.\n");
1148 value = strstr(tmp,",");
1150 ast_log(AST_LOG_WARNING, "variable has bad format.\n");
1153 new = alloca((strlen(value)+strlen(newpassword)+1));
1154 sprintf(new,"%s%s", newpassword, value);
1155 if (!(cat = ast_category_get(cfg, category))) {
1156 ast_log(AST_LOG_WARNING, "Failed to get category structure.\n");
1159 ast_variable_update(cat, vmu->mailbox, new, NULL, 0);
1162 /* save the results */
1163 reset_user_pw(vmu->context, vmu->mailbox, newpassword);
1164 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
1165 config_text_file_save(VOICEMAIL_CONFIG, cfg, "AppVoicemail");
1169 /* check users.conf and update the password stored for the mailbox*/
1170 /* if no vmsecret entry exists create one. */
1171 if ((cfg = ast_config_load("users.conf", config_flags))) {
1172 ast_debug(4, "we are looking for %s\n", vmu->mailbox);
1173 while ((category = ast_category_browse(cfg, category))) {
1174 ast_debug(4, "users.conf: %s\n", category);
1175 if (!strcasecmp(category, vmu->mailbox)) {
1176 if (!(tmp = ast_variable_retrieve(cfg, category, "vmsecret"))) {
1177 ast_debug(3, "looks like we need to make vmsecret!\n");
1178 var = ast_variable_new("vmsecret", newpassword, "");
1180 new = alloca(strlen(newpassword)+1);
1181 sprintf(new, "%s", newpassword);
1182 if (!(cat = ast_category_get(cfg, category))) {
1183 ast_debug(4, "failed to get category!\n");
1187 ast_variable_update(cat, "vmsecret", new, NULL, 0);
1189 ast_variable_append(cat, var);
1192 /* save the results and clean things up */
1193 reset_user_pw(vmu->context, vmu->mailbox, newpassword);
1194 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
1195 config_text_file_save("users.conf", cfg, "AppVoicemail");
1199 static void vm_change_password_shell(struct ast_vm_user *vmu, char *newpassword)
1202 snprintf(buf,255,"%s %s %s %s",ext_pass_cmd,vmu->context,vmu->mailbox,newpassword);
1203 if (!ast_safe_system(buf)) {
1204 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
1205 /* Reset the password in memory, too */
1206 reset_user_pw(vmu->context, vmu->mailbox, newpassword);
1211 * \brief Creates a file system path expression for a folder within the voicemail data folder and the appropriate context.
1212 * \param dest The variable to hold the output generated path expression. This buffer should be of size PATH_MAX.
1213 * \param len The length of the path string that was written out.
1215 * The path is constructed as
1216 * VM_SPOOL_DIRcontext/ext/folder
1218 * \return zero on success, -1 on error.
1220 static int make_dir(char *dest, int len, const char *context, const char *ext, const char *folder)
1222 return snprintf(dest, len, "%s%s/%s/%s", VM_SPOOL_DIR, context, ext, folder);
1226 static int make_gsm_file(char *dest, size_t len, char *imapuser, char *dir, int num, char *prefix)
1229 if ((res = ast_mkdir(dir, 01777))) {
1230 ast_log(AST_LOG_WARNING, "ast_mkdir '%s' failed: %s\n", dir, strerror(res));
1231 return snprintf(dest, len, "%s/%smsg%04d", dir, prefix, num);
1233 return snprintf(dest, len, "%s/%smsg%04d", dir, prefix, num);
1236 static void vm_imap_delete(int msgnum, struct vm_state *vms)
1238 unsigned long messageNum = 0;
1241 /* find real message number based on msgnum */
1242 /* this may be an index into vms->msgArray based on the msgnum. */
1244 messageNum = vms->msgArray[msgnum];
1245 if (messageNum == 0) {
1246 ast_log(AST_LOG_WARNING, "msgnum %d, mailbox message %lu is zero.\n", msgnum, messageNum);
1249 ast_debug(3, "deleting msgnum %d, which is mailbox message %lu\n",msgnum,messageNum);
1250 /* delete message */
1251 snprintf (arg, sizeof(arg), "%lu",messageNum);
1252 mail_setflag (vms->mailstream,arg,"\\DELETED");
1256 static int make_file(char *dest, int len, char *dir, int num)
1258 return snprintf(dest, len, "%s/msg%04d", dir, num);
1261 /*! \brief basically mkdir -p $dest/$context/$ext/$folder
1262 * \param dest String. base directory.
1263 * \param len Length of dest.
1264 * \param context String. Ignored if is null or empty string.
1265 * \param ext String. Ignored if is null or empty string.
1266 * \param folder String. Ignored if is null or empty string.
1267 * \return -1 on failure, 0 on success.
1269 static int create_dirpath(char *dest, int len, const char *context, const char *ext, const char *folder)
1271 mode_t mode = VOICEMAIL_DIR_MODE;
1274 make_dir(dest, len, context, ext, folder);
1275 if ((res = ast_mkdir(dest, mode))) {
1276 ast_log(AST_LOG_WARNING, "ast_mkdir '%s' failed: %s\n", dest, strerror(res));
1282 /*! \brief Lock file path
1283 only return failure if ast_lock_path returns 'timeout',
1284 not if the path does not exist or any other reason
1286 static int vm_lock_path(const char *path)
1288 switch (ast_lock_path(path)) {
1289 case AST_LOCK_TIMEOUT:
1298 struct generic_prepare_struct {
1304 static SQLHSTMT generic_prepare(struct odbc_obj *obj, void *data)
1306 struct generic_prepare_struct *gps = data;
1310 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
1311 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1312 ast_log(AST_LOG_WARNING, "SQL Alloc Handle failed!\n");
1315 res = SQLPrepare(stmt, (unsigned char *)gps->sql, SQL_NTS);
1316 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1317 ast_log(AST_LOG_WARNING, "SQL Prepare failed![%s]\n", gps->sql);
1318 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
1321 for (i = 0; i < gps->argc; i++)
1322 SQLBindParameter(stmt, i + 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(gps->argv[i]), 0, gps->argv[i], 0, NULL);
1328 * \brief Retrieves a file from an ODBC data store.
1329 * \param dir the path to the file to be retreived.
1330 * \param msgnum the message number, such as within a mailbox folder.
1332 * This method is used by the RETRIEVE macro when mailboxes are stored in an ODBC back end.
1333 * 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.
1335 * The file is looked up by invoking a SQL on the odbc_table (default 'voicemessages') using the dir and msgnum input parameters.
1336 * The output is the message information file with the name msgnum and the extension .txt
1337 * and the message file with the extension of its format, in the directory with base file name of the msgnum.
1339 * \return 0 on success, -1 on error.
1341 static int retrieve_file(char *dir, int msgnum)
1347 void *fdm = MAP_FAILED;
1348 SQLSMALLINT colcount=0;
1355 SQLSMALLINT datatype;
1356 SQLSMALLINT decimaldigits;
1357 SQLSMALLINT nullable;
1363 char full_fn[PATH_MAX];
1365 char *argv[] = { dir, msgnums };
1366 struct generic_prepare_struct gps = { .sql = sql, .argc = 2, .argv = argv };
1368 struct odbc_obj *obj;
1369 obj = ast_odbc_request_obj(odbc_database, 0);
1371 ast_copy_string(fmt, vmfmts, sizeof(fmt));
1372 c = strchr(fmt, '|');
1375 if (!strcasecmp(fmt, "wav49"))
1377 snprintf(msgnums, sizeof(msgnums),"%d", msgnum);
1379 make_file(fn, sizeof(fn), dir, msgnum);
1381 ast_copy_string(fn, dir, sizeof(fn));
1383 /* Create the information file */
1384 snprintf(full_fn, sizeof(full_fn), "%s.txt", fn);
1386 if (!(f = fopen(full_fn, "w+"))) {
1387 ast_log(AST_LOG_WARNING, "Failed to open/create '%s'\n", full_fn);
1391 snprintf(full_fn, sizeof(full_fn), "%s.%s", fn, fmt);
1392 snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE dir=? AND msgnum=?",odbc_table);
1393 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
1395 ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
1396 ast_odbc_release_obj(obj);
1399 res = SQLFetch(stmt);
1400 if (res == SQL_NO_DATA) {
1401 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1402 ast_odbc_release_obj(obj);
1404 } else if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1405 ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
1406 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1407 ast_odbc_release_obj(obj);
1410 fd = open(full_fn, O_RDWR | O_CREAT | O_TRUNC, VOICEMAIL_FILE_MODE);
1412 ast_log(AST_LOG_WARNING, "Failed to write '%s': %s\n", full_fn, strerror(errno));
1413 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1414 ast_odbc_release_obj(obj);
1417 res = SQLNumResultCols(stmt, &colcount);
1418 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1419 ast_log(AST_LOG_WARNING, "SQL Column Count error!\n[%s]\n\n", sql);
1420 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1421 ast_odbc_release_obj(obj);
1425 fprintf(f, "[message]\n");
1426 for (x=0;x<colcount;x++) {
1428 collen = sizeof(coltitle);
1429 res = SQLDescribeCol(stmt, x + 1, (unsigned char *)coltitle, sizeof(coltitle), &collen,
1430 &datatype, &colsize, &decimaldigits, &nullable);
1431 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1432 ast_log(AST_LOG_WARNING, "SQL Describe Column error!\n[%s]\n\n", sql);
1433 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1434 ast_odbc_release_obj(obj);
1437 if (!strcasecmp(coltitle, "recording")) {
1439 res = SQLGetData(stmt, x + 1, SQL_BINARY, rowdata, 0, &colsize2);
1443 lseek(fd, fdlen - 1, SEEK_SET);
1444 if (write(fd, tmp, 1) != 1) {
1449 /* Read out in small chunks */
1450 for (offset = 0; offset < colsize2; offset += CHUNKSIZE) {
1451 if ((fdm = mmap(NULL, CHUNKSIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, offset)) == MAP_FAILED) {
1452 ast_log(AST_LOG_WARNING, "Could not mmap the output file: %s (%d)\n", strerror(errno), errno);
1453 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
1454 ast_odbc_release_obj(obj);
1457 res = SQLGetData(stmt, x + 1, SQL_BINARY, fdm, CHUNKSIZE, NULL);
1458 munmap(fdm, CHUNKSIZE);
1459 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1460 ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
1462 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
1463 ast_odbc_release_obj(obj);
1468 truncate(full_fn, fdlen);
1471 res = SQLGetData(stmt, x + 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
1472 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1473 ast_log(AST_LOG_WARNING, "SQL Get Data error! coltitle=%s\n[%s]\n\n", coltitle, sql);
1474 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1475 ast_odbc_release_obj(obj);
1478 if (strcasecmp(coltitle, "msgnum") && strcasecmp(coltitle, "dir") && f)
1479 fprintf(f, "%s=%s\n", coltitle, rowdata);
1482 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1483 ast_odbc_release_obj(obj);
1485 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
1495 * \brief Removes a voicemail message file.
1496 * \param dir the path to the message file.
1497 * \param msgnum the unique number for the message within the mailbox.
1499 * Removes the message content file and the information file.
1500 * This method is used by the DISPOSE macro when mailboxes are stored in an ODBC back end.
1501 * Typical use is to clean up after a RETRIEVE operation.
1502 * Note that this does not remove the message from the mailbox folders, to do that we would use delete_file().
1503 * \return zero on success, -1 on error.
1505 static int remove_file(char *dir, int msgnum)
1508 char full_fn[PATH_MAX];
1512 snprintf(msgnums, sizeof(msgnums), "%d", msgnum);
1513 make_file(fn, sizeof(fn), dir, msgnum);
1515 ast_copy_string(fn, dir, sizeof(fn));
1516 ast_filedelete(fn, NULL);
1517 if (ast_check_realtime("voicemail_data")) {
1518 ast_destroy_realtime("voicemail_data", "filename", fn, NULL);
1520 snprintf(full_fn, sizeof(full_fn), "%s.txt", fn);
1526 * \brief Determines the highest message number in use for a given user and mailbox folder.
1528 * \param dir the folder the mailbox folder to look for messages. Used to construct the SQL where clause.
1530 * This method is used when mailboxes are stored in an ODBC back end.
1531 * Typical use to set the msgnum would be to take the value returned from this method and add one to it.
1533 * \return the value of zero or greaterto indicate the last message index in use, -1 to indicate none.
1535 static int last_message_index(struct ast_vm_user *vmu, char *dir)
1542 char *argv[] = { dir };
1543 struct generic_prepare_struct gps = { .sql = sql, .argc = 1, .argv = argv };
1545 struct odbc_obj *obj;
1546 obj = ast_odbc_request_obj(odbc_database, 0);
1548 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir=?",odbc_table);
1549 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
1551 ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
1552 ast_odbc_release_obj(obj);
1555 res = SQLFetch(stmt);
1556 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1557 ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
1558 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1559 ast_odbc_release_obj(obj);
1562 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
1563 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1564 ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
1565 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1566 ast_odbc_release_obj(obj);
1569 if (sscanf(rowdata, "%d", &x) != 1)
1570 ast_log(AST_LOG_WARNING, "Failed to read message count!\n");
1571 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1572 ast_odbc_release_obj(obj);
1574 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
1580 * \brief Determines if the specified message exists.
1581 * \param dir the folder the mailbox folder to look for messages.
1582 * \param msgnum the message index to query for.
1584 * This method is used when mailboxes are stored in an ODBC back end.
1586 * \return greater than zero if the message exists, zero when the message does not exist or on error.
1588 static int message_exists(char *dir, int msgnum)
1596 char *argv[] = { dir, msgnums };
1597 struct generic_prepare_struct gps = { .sql = sql, .argc = 2, .argv = argv };
1599 struct odbc_obj *obj;
1600 obj = ast_odbc_request_obj(odbc_database, 0);
1602 snprintf(msgnums, sizeof(msgnums), "%d", msgnum);
1603 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir=? AND msgnum=?",odbc_table);
1604 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
1606 ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
1607 ast_odbc_release_obj(obj);
1610 res = SQLFetch(stmt);
1611 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1612 ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
1613 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1614 ast_odbc_release_obj(obj);
1617 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
1618 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1619 ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
1620 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1621 ast_odbc_release_obj(obj);
1624 if (sscanf(rowdata, "%d", &x) != 1)
1625 ast_log(AST_LOG_WARNING, "Failed to read message count!\n");
1626 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1627 ast_odbc_release_obj(obj);
1629 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
1635 * \brief returns the one-based count for messages.
1637 * \param dir the folder the mailbox folder to look for messages. Used to construct the SQL where clause.
1639 * This method is used when mailboxes are stored in an ODBC back end.
1640 * The message index is zero-based, the first message will be index 0. For convenient display it is good to have the
1641 * one-based messages.
1642 * This method just calls last_message_index and returns +1 of its value.
1644 * \return the value greater than zero on success to indicate the one-based count of messages, less than zero on error.
1646 static int count_messages(struct ast_vm_user *vmu, char *dir)
1648 return last_message_index(vmu, dir) + 1;
1652 * \brief Deletes a message from the mailbox folder.
1653 * \param sdir The mailbox folder to work in.
1654 * \param smsg The message index to be deleted.
1656 * This method is used when mailboxes are stored in an ODBC back end.
1657 * The specified message is directly deleted from the database 'voicemessages' table.
1659 * \return the value greater than zero on success to indicate the number of messages, less than zero on error.
1661 static void delete_file(char *sdir, int smsg)
1666 char *argv[] = { sdir, msgnums };
1667 struct generic_prepare_struct gps = { .sql = sql, .argc = 2, .argv = argv };
1669 struct odbc_obj *obj;
1670 obj = ast_odbc_request_obj(odbc_database, 0);
1672 snprintf(msgnums, sizeof(msgnums), "%d", smsg);
1673 snprintf(sql, sizeof(sql), "DELETE FROM %s WHERE dir=? AND msgnum=?",odbc_table);
1674 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
1676 ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
1678 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1679 ast_odbc_release_obj(obj);
1681 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
1686 * \brief Copies a voicemail from one mailbox to another.
1687 * \param sdir the folder for which to look for the message to be copied.
1688 * \param smsg the index of the message to be copied.
1689 * \param ddir the destination folder to copy the message into.
1690 * \param dmsg the index to be used for the copied message.
1691 * \param dmailboxuser The user who owns the mailbox tha contains the destination folder.
1692 * \param dmailboxcontext The context for the destination user.
1694 * This method is used for the COPY macro when mailboxes are stored in an ODBC back end.
1696 static void copy_file(char *sdir, int smsg, char *ddir, int dmsg, char *dmailboxuser, char *dmailboxcontext)
1702 struct odbc_obj *obj;
1703 char *argv[] = { ddir, msgnumd, dmailboxuser, dmailboxcontext, sdir, msgnums };
1704 struct generic_prepare_struct gps = { .sql = sql, .argc = 6, .argv = argv };
1706 delete_file(ddir, dmsg);
1707 obj = ast_odbc_request_obj(odbc_database, 0);
1709 snprintf(msgnums, sizeof(msgnums), "%d", smsg);
1710 snprintf(msgnumd, sizeof(msgnumd), "%d", dmsg);
1711 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);
1712 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
1714 ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s] (You probably don't have MySQL 4.1 or later installed)\n\n", sql);
1716 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
1717 ast_odbc_release_obj(obj);
1719 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
1723 struct insert_data {
1729 const char *context;
1730 const char *macrocontext;
1731 const char *callerid;
1732 const char *origtime;
1733 const char *duration;
1735 char *mailboxcontext;
1736 const char *category;
1740 static SQLHSTMT insert_data_cb(struct odbc_obj *obj, void *vdata)
1742 struct insert_data *data = vdata;
1745 SQLLEN len = data->datalen;
1747 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
1748 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1749 ast_log(AST_LOG_WARNING, "SQL Alloc Handle failed!\n");
1750 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
1754 SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->dir), 0, (void *)data->dir, 0, NULL);
1755 SQLBindParameter(stmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->msgnums), 0, (void *)data->msgnums, 0, NULL);
1756 SQLBindParameter(stmt, 3, SQL_PARAM_INPUT, SQL_C_BINARY, SQL_LONGVARBINARY, data->datalen, 0, (void *)data->data, data->datalen, &len);
1757 SQLBindParameter(stmt, 4, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->context), 0, (void *)data->context, 0, NULL);
1758 SQLBindParameter(stmt, 5, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->macrocontext), 0, (void *)data->macrocontext, 0, NULL);
1759 SQLBindParameter(stmt, 6, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->callerid), 0, (void *)data->callerid, 0, NULL);
1760 SQLBindParameter(stmt, 7, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->origtime), 0, (void *)data->origtime, 0, NULL);
1761 SQLBindParameter(stmt, 8, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->duration), 0, (void *)data->duration, 0, NULL);
1762 SQLBindParameter(stmt, 9, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->mailboxuser), 0, (void *)data->mailboxuser, 0, NULL);
1763 SQLBindParameter(stmt, 10, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->mailboxcontext), 0, (void *)data->mailboxcontext, 0, NULL);
1764 SQLBindParameter(stmt, 11, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->flag), 0, (void *)data->flag, 0, NULL);
1765 if (!ast_strlen_zero(data->category)) {
1766 SQLBindParameter(stmt, 12, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->category), 0, (void *)data->category, 0, NULL);
1768 res = SQLExecDirect(stmt, (unsigned char *)data->sql, SQL_NTS);
1769 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1770 ast_log(AST_LOG_WARNING, "SQL Direct Execute failed!\n");
1771 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
1779 * \brief Stores a voicemail into the database.
1780 * \param dir the folder the mailbox folder to store the message.
1781 * \param mailboxuser the user owning the mailbox folder.
1782 * \param mailboxcontext
1783 * \param msgnum the message index for the message to be stored.
1785 * This method is used when mailboxes are stored in an ODBC back end.
1786 * The message sound file and information file is looked up on the file system.
1787 * A SQL query is invoked to store the message into the (MySQL) database.
1789 * \return the zero on success -1 on error.
1791 static int store_file(char *dir, char *mailboxuser, char *mailboxcontext, int msgnum)
1795 void *fdm = MAP_FAILED;
1801 char full_fn[PATH_MAX];
1804 struct ast_config *cfg=NULL;
1805 struct odbc_obj *obj;
1806 struct insert_data idata = { .sql = sql, .msgnums = msgnums, .dir = dir, .mailboxuser = mailboxuser, .mailboxcontext = mailboxcontext };
1807 struct ast_flags config_flags = { CONFIG_FLAG_NOCACHE };
1809 delete_file(dir, msgnum);
1810 if (!(obj = ast_odbc_request_obj(odbc_database, 0))) {
1811 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
1816 ast_copy_string(fmt, vmfmts, sizeof(fmt));
1817 c = strchr(fmt, '|');
1820 if (!strcasecmp(fmt, "wav49"))
1822 snprintf(msgnums, sizeof(msgnums),"%d", msgnum);
1824 make_file(fn, sizeof(fn), dir, msgnum);
1826 ast_copy_string(fn, dir, sizeof(fn));
1827 snprintf(full_fn, sizeof(full_fn), "%s.txt", fn);
1828 cfg = ast_config_load(full_fn, config_flags);
1829 snprintf(full_fn, sizeof(full_fn), "%s.%s", fn, fmt);
1830 fd = open(full_fn, O_RDWR);
1832 ast_log(AST_LOG_WARNING, "Open of sound file '%s' failed: %s\n", full_fn, strerror(errno));
1837 if (!(idata.context = ast_variable_retrieve(cfg, "message", "context"))) {
1840 if (!(idata.macrocontext = ast_variable_retrieve(cfg, "message", "macrocontext"))) {
1841 idata.macrocontext = "";
1843 if (!(idata.callerid = ast_variable_retrieve(cfg, "message", "callerid"))) {
1844 idata.callerid = "";
1846 if (!(idata.origtime = ast_variable_retrieve(cfg, "message", "origtime"))) {
1847 idata.origtime = "";
1849 if (!(idata.duration = ast_variable_retrieve(cfg, "message", "duration"))) {
1850 idata.duration = "";
1852 if (!(idata.category = ast_variable_retrieve(cfg, "message", "category"))) {
1853 idata.category = "";
1855 if (!(idata.flag = ast_variable_retrieve(cfg, "message", "flag"))) {
1859 fdlen = lseek(fd, 0, SEEK_END);
1860 lseek(fd, 0, SEEK_SET);
1861 printf("Length is %zd\n", fdlen);
1862 fdm = mmap(NULL, fdlen, PROT_READ | PROT_WRITE, MAP_SHARED,fd, 0);
1863 if (fdm == MAP_FAILED) {
1864 ast_log(AST_LOG_WARNING, "Memory map failed!\n");
1869 idata.datalen = fdlen;
1871 if (!ast_strlen_zero(idata.category))
1872 snprintf(sql, sizeof(sql), "INSERT INTO %s (dir,msgnum,recording,context,macrocontext,callerid,origtime,duration,mailboxuser,mailboxcontext,flag,category) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)",odbc_table);
1874 snprintf(sql, sizeof(sql), "INSERT INTO %s (dir,msgnum,recording,context,macrocontext,callerid,origtime,duration,mailboxuser,mailboxcontext,flag) VALUES (?,?,?,?,?,?,?,?,?,?,?)",odbc_table);
1876 if ((stmt = ast_odbc_direct_execute(obj, insert_data_cb, &idata))) {
1877 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1879 ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
1884 ast_odbc_release_obj(obj);
1887 ast_config_destroy(cfg);
1888 if (fdm != MAP_FAILED)
1896 * \brief Renames a message in a mailbox folder.
1897 * \param sdir The folder of the message to be renamed.
1898 * \param smsg The index of the message to be renamed.
1899 * \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.
1900 * \param mailboxcontext The context to be set for the message. Usually this will be the same as the original context.
1901 * \param ddir The destination folder for the message to be renamed into
1902 * \param dmsg The destination message for the message to be renamed.
1904 * This method is used by the RENAME macro when mailboxes are stored in an ODBC back end.
1905 * 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.
1906 * 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.
1908 static void rename_file(char *sdir, int smsg, char *mailboxuser, char *mailboxcontext, char *ddir, int dmsg)
1914 struct odbc_obj *obj;
1915 char *argv[] = { ddir, msgnumd, mailboxuser, mailboxcontext, sdir, msgnums };
1916 struct generic_prepare_struct gps = { .sql = sql, .argc = 6, .argv = argv };
1918 delete_file(ddir, dmsg);
1919 obj = ast_odbc_request_obj(odbc_database, 0);
1921 snprintf(msgnums, sizeof(msgnums), "%d", smsg);
1922 snprintf(msgnumd, sizeof(msgnumd), "%d", dmsg);
1923 snprintf(sql, sizeof(sql), "UPDATE %s SET dir=?, msgnum=?, mailboxuser=?, mailboxcontext=? WHERE dir=? AND msgnum=?",odbc_table);
1924 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
1926 ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
1928 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
1929 ast_odbc_release_obj(obj);
1931 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
1936 #ifndef IMAP_STORAGE
1938 * \brief Find all .txt files - even if they are not in sequence from 0000.
1942 * This method is used when mailboxes are stored on the filesystem. (not ODBC and not IMAP).
1944 * \return the count of messages, zero or more.
1946 static int count_messages(struct ast_vm_user *vmu, char *dir)
1951 struct dirent *vment = NULL;
1953 if (vm_lock_path(dir))
1954 return ERROR_LOCK_PATH;
1956 if ((vmdir = opendir(dir))) {
1957 while ((vment = readdir(vmdir))) {
1958 if (strlen(vment->d_name) > 7 && !strncmp(vment->d_name + 7, ".txt", 4)) {
1964 ast_unlock_path(dir);
1970 * \brief Renames a message in a mailbox folder.
1971 * \param sfn The path to the mailbox information and data file to be renamed.
1972 * \param dfn The path for where the message data and information files will be renamed to.
1974 * This method is used by the RENAME macro when mailboxes are stored on the filesystem. (not ODBC and not IMAP).
1976 static void rename_file(char *sfn, char *dfn)
1978 char stxt[PATH_MAX];
1979 char dtxt[PATH_MAX];
1980 ast_filerename(sfn,dfn,NULL);
1981 snprintf(stxt, sizeof(stxt), "%s.txt", sfn);
1982 snprintf(dtxt, sizeof(dtxt), "%s.txt", dfn);
1983 if (ast_check_realtime("voicemail_data")) {
1984 ast_update_realtime("voicemail_data", "filename", sfn, "filename", dfn, NULL);
1990 * \brief Determines the highest message number in use for a given user and mailbox folder.
1992 * \param dir the folder the mailbox folder to look for messages. Used to construct the SQL where clause.
1994 * This method is used when mailboxes are stored on the filesystem. (not ODBC and not IMAP).
1995 * Typical use to set the msgnum would be to take the value returned from this method and add one to it.
1997 * \note Should always be called with a lock already set on dir.
1998 * \return the value of zero or greaterto indicate the last message index in use, -1 to indicate none.
2000 static int last_message_index(struct ast_vm_user *vmu, char *dir)
2003 unsigned char map[MAXMSGLIMIT] = "";
2005 struct dirent *msgdirent;
2008 /* Reading the entire directory into a file map scales better than
2009 * doing a stat repeatedly on a predicted sequence. I suspect this
2010 * is partially due to stat(2) internally doing a readdir(2) itself to
2011 * find each file. */
2012 msgdir = opendir(dir);
2013 while ((msgdirent = readdir(msgdir))) {
2014 if (sscanf(msgdirent->d_name, "msg%d", &msgdirint) == 1 && msgdirint < MAXMSGLIMIT)
2019 for (x = 0; x < vmu->maxmsg; x++) {
2029 * \brief Utility function to copy a file.
2030 * \param infile The path to the file to be copied. The file must be readable, it is opened in read only mode.
2031 * \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.
2033 * 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.
2034 * The copy operation copies up to 4096 bytes at once.
2036 * \return zero on success, -1 on error.
2038 static int copy(char *infile, char *outfile)
2046 #ifdef HARDLINK_WHEN_POSSIBLE
2047 /* Hard link if possible; saves disk space & is faster */
2048 if (link(infile, outfile)) {
2050 if ((ifd = open(infile, O_RDONLY)) < 0) {
2051 ast_log(AST_LOG_WARNING, "Unable to open %s in read-only mode\n", infile);
2054 if ((ofd = open(outfile, O_WRONLY | O_TRUNC | O_CREAT, VOICEMAIL_FILE_MODE)) < 0) {
2055 ast_log(AST_LOG_WARNING, "Unable to open %s in write-only mode\n", outfile);
2060 len = read(ifd, buf, sizeof(buf));
2062 ast_log(AST_LOG_WARNING, "Read failed on %s: %s\n", infile, strerror(errno));
2068 res = write(ofd, buf, len);
2069 if (errno == ENOMEM || errno == ENOSPC || res != len) {
2070 ast_log(AST_LOG_WARNING, "Write failed on %s (%d of %d): %s\n", outfile, res, len, strerror(errno));
2080 #ifdef HARDLINK_WHEN_POSSIBLE
2082 /* Hard link succeeded */
2089 * \brief Copies a voicemail information (envelope) file.
2093 * Every voicemail has the data (.wav) file, and the information file.
2094 * This function performs the file system copying of the information file for a voicemail, handling the internal fields and their values.
2095 * This is used by the COPY macro when not using IMAP storage.
2097 static void copy_plain_file(char *frompath, char *topath)
2099 char frompath2[PATH_MAX], topath2[PATH_MAX];
2100 struct ast_variable *tmp,*var = NULL;
2101 const char *origmailbox = NULL, *context = NULL, *macrocontext = NULL, *exten = NULL, *priority = NULL, *callerchan = NULL, *callerid = NULL, *origdate = NULL, *origtime = NULL, *category = NULL, *duration = NULL;
2102 ast_filecopy(frompath, topath, NULL);
2103 snprintf(frompath2, sizeof(frompath2), "%s.txt", frompath);
2104 snprintf(topath2, sizeof(topath2), "%s.txt", topath);
2105 if (ast_check_realtime("voicemail_data")) {
2106 var = ast_load_realtime("voicemail_data", "filename", frompath, NULL);
2107 /* This cycle converts ast_variable linked list, to va_list list of arguments, may be there is a better way to do it? */
2108 for (tmp = var; tmp; tmp = tmp->next) {
2109 if (!strcasecmp(tmp->name, "origmailbox")) {
2110 origmailbox = tmp->value;
2111 } else if (!strcasecmp(tmp->name, "context")) {
2112 context = tmp->value;
2113 } else if (!strcasecmp(tmp->name, "macrocontext")) {
2114 macrocontext = tmp->value;
2115 } else if (!strcasecmp(tmp->name, "exten")) {
2117 } else if (!strcasecmp(tmp->name, "priority")) {
2118 priority = tmp->value;
2119 } else if (!strcasecmp(tmp->name, "callerchan")) {
2120 callerchan = tmp->value;
2121 } else if (!strcasecmp(tmp->name, "callerid")) {
2122 callerid = tmp->value;
2123 } else if (!strcasecmp(tmp->name, "origdate")) {
2124 origdate = tmp->value;
2125 } else if (!strcasecmp(tmp->name, "origtime")) {
2126 origtime = tmp->value;
2127 } else if (!strcasecmp(tmp->name, "category")) {
2128 category = tmp->value;
2129 } else if (!strcasecmp(tmp->name, "duration")) {
2130 duration = tmp->value;
2133 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, NULL);
2135 copy(frompath2, topath2);
2136 ast_variables_destroy(var);
2139 #endif /* #ifndef IMAP_STORAGE */
2140 #endif /* #else of #ifdef ODBC_STORAGE */
2141 #ifndef ODBC_STORAGE
2143 * \brief Removes the voicemail sound and information file.
2144 * \param file The path to the sound file. This will be the the folder and message index, without the extension.
2146 * This is used by the DELETE macro when voicemails are stored on the file system.
2148 * \return zero on success, -1 on error.
2150 static int vm_delete(char *file)
2155 txtsize = (strlen(file) + 5)*sizeof(char);
2156 txt = alloca(txtsize);
2157 /* Sprintf here would safe because we alloca'd exactly the right length,
2158 * but trying to eliminate all sprintf's anyhow
2160 if (ast_check_realtime("voicemail_data")) {
2161 ast_destroy_realtime("voicemail_data", "filename", file, NULL);
2163 snprintf(txt, txtsize, "%s.txt", file);
2165 return ast_filedelete(file, NULL);
2170 * \brief utility used by inchar(), for base_encode()
2172 static int inbuf(struct baseio *bio, FILE *fi)
2179 if ((l = fread(bio->iobuf,1,BASEMAXINLINE,fi)) <= 0) {
2194 * \brief utility used by base_encode()
2196 static int inchar(struct baseio *bio, FILE *fi)
2198 if (bio->iocp>=bio->iolen) {
2199 if (!inbuf(bio, fi))
2203 return bio->iobuf[bio->iocp++];
2207 * \brief utility used by base_encode()
2209 static int ochar(struct baseio *bio, int c, FILE *so)
2211 if (bio->linelength >= BASELINELEN) {
2212 if (fputs(eol,so) == EOF)
2218 if (putc(((unsigned char)c),so) == EOF)
2227 * \brief Performs a base 64 encode algorithm on the contents of a File
2228 * \param filename The path to the file to be encoded. Must be readable, file is opened in read mode.
2229 * \param so A FILE handle to the output file to receive the base 64 encoded contents of the input file, identified by filename.
2231 * 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 ?
2233 * \return zero on success, -1 on error.
2235 static int base_encode(char *filename, FILE *so)
2237 static const unsigned char dtable[] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K',
2238 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
2239 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0',
2240 '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'};
2245 memset(&bio, 0, sizeof(bio));
2246 bio.iocp = BASEMAXINLINE;
2248 if (!(fi = fopen(filename, "rb"))) {
2249 ast_log(AST_LOG_WARNING, "Failed to open file: %s: %s\n", filename, strerror(errno));
2254 unsigned char igroup[3], ogroup[4];
2257 igroup[0]= igroup[1]= igroup[2]= 0;
2259 for (n= 0;n<3;n++) {
2260 if ((c = inchar(&bio, fi)) == EOF) {
2265 igroup[n]= (unsigned char)c;
2269 ogroup[0]= dtable[igroup[0]>>2];
2270 ogroup[1]= dtable[((igroup[0]&3)<<4) | (igroup[1]>>4)];
2271 ogroup[2]= dtable[((igroup[1]&0xF)<<2) | (igroup[2]>>6)];
2272 ogroup[3]= dtable[igroup[2]&0x3F];
2282 ochar(&bio, ogroup[i], so);
2288 if (fputs(eol,so)==EOF)
2294 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)
2297 /* Prepare variables for substitution in email body and subject */
2298 pbx_builtin_setvar_helper(ast, "VM_NAME", vmu->fullname);
2299 pbx_builtin_setvar_helper(ast, "VM_DUR", dur);
2300 snprintf(passdata, passdatasize, "%d", msgnum);
2301 pbx_builtin_setvar_helper(ast, "VM_MSGNUM", passdata);
2302 pbx_builtin_setvar_helper(ast, "VM_CONTEXT", context);
2303 pbx_builtin_setvar_helper(ast, "VM_MAILBOX", mailbox);
2304 pbx_builtin_setvar_helper(ast, "VM_CALLERID", ast_callerid_merge(callerid, sizeof(callerid), cidname, cidnum, "Unknown Caller"));
2305 pbx_builtin_setvar_helper(ast, "VM_CIDNAME", (cidname ? cidname : "an unknown caller"));
2306 pbx_builtin_setvar_helper(ast, "VM_CIDNUM", (cidnum ? cidnum : "an unknown caller"));
2307 pbx_builtin_setvar_helper(ast, "VM_DATE", date);
2308 pbx_builtin_setvar_helper(ast, "VM_CATEGORY", category ? ast_strdupa(category) : "no category");
2309 pbx_builtin_setvar_helper(ast, "VM_FLAG", flag);
2313 * \brief Wraps a character sequence in double quotes, escaping occurences of quotes within the string.
2314 * \param from The string to work with.
2315 * \param to The string to write the modified quoted string. This buffer should be sufficiently larger than the from string, so as to allow it to be expanded by the surrounding quotes and escaping of internal quotes.
2317 * \return The destination string with quotes wrapped on it (the to field).
2319 static char *quote(const char *from, char *to, size_t len)
2323 for (; ptr < to + len - 1; from++) {
2326 else if (*from == '\0')
2330 if (ptr < to + len - 1)
2337 * fill in *tm for current time according to the proper timezone, if any.
2338 * Return tm so it can be used as a function argument.
2340 static const struct ast_tm *vmu_tm(const struct ast_vm_user *vmu, struct ast_tm *tm)
2342 const struct vm_zone *z = NULL;
2343 struct timeval t = ast_tvnow();
2345 /* Does this user have a timezone specified? */
2346 if (!ast_strlen_zero(vmu->zonetag)) {
2347 /* Find the zone in the list */
2348 AST_LIST_LOCK(&zones);
2349 AST_LIST_TRAVERSE(&zones, z, list) {
2350 if (!strcmp(z->name, vmu->zonetag))
2353 AST_LIST_UNLOCK(&zones);
2355 ast_localtime(&t, tm, z ? z->timezone : NULL);
2359 /*! \brief same as mkstemp, but return a FILE * */
2360 static FILE *vm_mkftemp(char *template)
2363 int pfd = mkstemp(template);
2364 chmod(template, VOICEMAIL_FILE_MODE & ~my_umask);
2366 p = fdopen(pfd, "w+");
2376 * \brief Creates the email file to be sent to indicate a new voicemail exists for a user.
2377 * \param p The output file to generate the email contents into.
2378 * \param srcemail The email address to send the email to, presumably the email address for the owner of the mailbox.
2379 * \param vmu The voicemail user who is sending the voicemail.
2380 * \param msgnum The message index in the mailbox folder.
2382 * \param mailbox The voicemail box to read the voicemail to be notified in this email.
2383 * \param cidnum The caller ID number.
2384 * \param cidname The caller ID name.
2385 * \param attach the name of the sound file to be attached to the email, if attach_user_voicemail == 1.
2386 * \param format The message sound file format. i.e. .wav
2387 * \param duration The time of the message content, in seconds.
2388 * \param attach_user_voicemail if 1, the sound file is attached to the email.
2391 * \param imap if == 1, indicates the target folder for the email notification to be sent to will be an IMAP mailstore. This causes additional mailbox headers to be set, which would facilitate searching for the email in the destination IMAP folder.
2393 * The email body, and base 64 encoded attachement (if any) are stored to the file identified by *p. This method does not actually send the email. That is done by invoking the configure 'mailcmd' and piping this generated file into it, or with the sendemail() function.
2395 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)
2398 char host[MAXHOSTNAMELEN] = "";
2404 size_t len_passdata;
2405 char *greeting_attachment;
2414 gethostname(host, sizeof(host)-1);
2416 if (strchr(srcemail, '@'))
2417 ast_copy_string(who, srcemail, sizeof(who));
2419 snprintf(who, sizeof(who), "%s@%s", srcemail, host);
2421 greeting_attachment = strrchr(ast_strdupa(attach), '/');
2422 if (greeting_attachment)
2423 *greeting_attachment++ = '\0';
2425 snprintf(dur, sizeof(dur), "%d:%02d", duration / 60, duration % 60);
2426 ast_strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", vmu_tm(vmu, &tm));
2427 fprintf(p, "Date: %s" ENDL, date);
2429 /* Set date format for voicemail mail */
2430 ast_strftime(date, sizeof(date), emaildateformat, &tm);
2432 if (!ast_strlen_zero(fromstring)) {
2433 struct ast_channel *ast;
2434 if ((ast = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, 0))) {
2436 int vmlen = strlen(fromstring)*3 + 200;
2437 passdata = alloca(vmlen);
2438 memset(passdata, 0, vmlen);
2439 prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category, flag);
2440 pbx_substitute_variables_helper(ast, fromstring, passdata, vmlen);
2441 len_passdata = strlen(passdata) * 2 + 3;
2442 passdata2 = alloca(len_passdata);
2443 fprintf(p, "From: %s <%s>" ENDL, quote(passdata, passdata2, len_passdata), who);
2444 ast_channel_free(ast);
2446 ast_log(AST_LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
2448 fprintf(p, "From: Asterisk PBX <%s>" ENDL, who);
2449 len_passdata = strlen(vmu->fullname) * 2 + 3;
2450 passdata2 = alloca(len_passdata);
2451 fprintf(p, "To: %s <%s>" ENDL, quote(vmu->fullname, passdata2, len_passdata), vmu->email);
2452 if (!ast_strlen_zero(emailsubject)) {
2453 struct ast_channel *ast;
2454 if ((ast = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, 0))) {
2456 int vmlen = strlen(emailsubject) * 3 + 200;
2457 passdata = alloca(vmlen);
2458 memset(passdata, 0, vmlen);
2459 prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category, flag);
2460 pbx_substitute_variables_helper(ast, emailsubject, passdata, vmlen);
2461 fprintf(p, "Subject: %s" ENDL, passdata);
2462 ast_channel_free(ast);
2464 ast_log(AST_LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
2465 } else if (ast_test_flag((&globalflags), VM_PBXSKIP)) {
2466 if (ast_strlen_zero(flag)) {
2467 fprintf(p, "Subject: New message %d in mailbox %s" ENDL, msgnum + 1, mailbox);
2469 fprintf(p, "Subject: New %s message %d in mailbox %s" ENDL, flag, msgnum + 1, mailbox);
2472 if (ast_strlen_zero(flag)) {
2473 fprintf(p, "Subject: [PBX]: New message %d in mailbox %s" ENDL, msgnum + 1, mailbox);
2475 fprintf(p, "Subject: [PBX]: New %s message %d in mailbox %s" ENDL, flag, msgnum + 1, mailbox);
2479 fprintf(p, "Message-ID: <Asterisk-%d-%d-%s-%d@%s>" ENDL, msgnum + 1, (unsigned int)ast_random(), mailbox, (int)getpid(), host);
2481 /* additional information needed for IMAP searching */
2482 fprintf(p, "X-Asterisk-VM-Message-Num: %d" ENDL, msgnum + 1);
2483 /* fprintf(p, "X-Asterisk-VM-Orig-Mailbox: %s" ENDL, ext); */
2484 fprintf(p, "X-Asterisk-VM-Server-Name: %s" ENDL, fromstring);
2485 fprintf(p, "X-Asterisk-VM-Context: %s" ENDL, context);
2486 fprintf(p, "X-Asterisk-VM-Extension: %s" ENDL, mailbox);
2487 /* flag added for Urgent */
2488 fprintf(p, "X-Asterisk-VM-Flag: %s" ENDL, flag);
2489 fprintf(p, "X-Asterisk-VM-Priority: %d" ENDL, chan->priority);
2490 fprintf(p, "X-Asterisk-VM-Caller-channel: %s" ENDL, chan->name);
2491 fprintf(p, "X-Asterisk-VM-Caller-ID-Num: %s" ENDL, cidnum);
2492 fprintf(p, "X-Asterisk-VM-Caller-ID-Name: %s" ENDL, cidname);
2493 fprintf(p, "X-Asterisk-VM-Duration: %d" ENDL, duration);
2494 if (!ast_strlen_zero(category))
2495 fprintf(p, "X-Asterisk-VM-Category: %s" ENDL, category);
2496 fprintf(p, "X-Asterisk-VM-Message-Type: %s" ENDL, msgnum > -1 ? "Message" : greeting_attachment);
2497 fprintf(p, "X-Asterisk-VM-Orig-date: %s" ENDL, date);
2498 fprintf(p, "X-Asterisk-VM-Orig-time: %ld" ENDL, (long)time(NULL));
2500 if (!ast_strlen_zero(cidnum))
2501 fprintf(p, "X-Asterisk-CallerID: %s" ENDL, cidnum);
2502 if (!ast_strlen_zero(cidname))
2503 fprintf(p, "X-Asterisk-CallerIDName: %s" ENDL, cidname);
2504 fprintf(p, "MIME-Version: 1.0" ENDL);
2505 if (attach_user_voicemail) {
2506 /* Something unique. */
2507 snprintf(bound, sizeof(bound), "----voicemail_%d%s%d%d", msgnum + 1, mailbox, (int)getpid(), (unsigned int)ast_random());
2509 fprintf(p, "Content-Type: multipart/mixed; boundary=\"%s\"" ENDL, bound);
2510 fprintf(p, ENDL ENDL "This is a multi-part message in MIME format." ENDL ENDL);
2511 fprintf(p, "--%s" ENDL, bound);
2513 fprintf(p, "Content-Type: text/plain; charset=%s" ENDL "Content-Transfer-Encoding: 8bit" ENDL ENDL, charset);
2515 struct ast_channel *ast;
2516 if ((ast = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, 0))) {
2518 int vmlen = strlen(emailbody)*3 + 200;
2519 passdata = alloca(vmlen);
2520 memset(passdata, 0, vmlen);
2521 prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category, flag);
2522 pbx_substitute_variables_helper(ast, emailbody, passdata, vmlen);
2523 fprintf(p, "%s" ENDL, passdata);
2524 ast_channel_free(ast);
2526 ast_log(AST_LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
2527 } else if (msgnum > -1){
2528 fprintf(p, "Dear %s:" ENDL ENDL "\tJust wanted to let you know you were just left a %s long %s message (number %d)" ENDL
2529 "in mailbox %s from %s, on %s so you might" ENDL
2530 "want to check it when you get a chance. Thanks!" ENDL ENDL "\t\t\t\t--Asterisk" ENDL ENDL, vmu->fullname,
2531 dur, flag, msgnum + 1, mailbox, (cidname ? cidname : (cidnum ? cidnum : "an unknown caller")), date);
2533 fprintf(p, "This message is to let you know that your greeting was changed on %s." ENDL
2534 "Please do not delete this message, lest your greeting vanish with it." ENDL ENDL, date);
2537 if (attach_user_voicemail) {
2538 if (!ast_strlen_zero(attach2)) {
2539 snprintf(filename, sizeof(filename), "msgintro%04d.%s", msgnum, format);
2540 ast_debug(5, "creating attachment filename %s\n", filename);
2541 add_email_attachment(p, vmu, format, attach2, greeting_attachment, mailbox, bound, filename, 0, msgnum);
2542 snprintf(filename, sizeof(filename), "msg%04d.%s", msgnum, format);
2543 ast_debug(5, "creating second attachment filename %s\n", filename);
2544 add_email_attachment(p, vmu, format, attach, greeting_attachment, mailbox, bound, filename, 1, msgnum);
2546 snprintf(filename, sizeof(filename), "msg%04d.%s", msgnum, format);
2547 ast_debug(5, "creating attachment filename %s, no second attachment.\n", filename);
2548 add_email_attachment(p, vmu, format, attach, greeting_attachment, mailbox, bound, filename, 1, msgnum);
2553 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)
2555 char tmpdir[256], newtmp[256];
2560 /* Eww. We want formats to tell us their own MIME type */
2561 char *ctype = (!strcasecmp(format, "ogg")) ? "application/" : "audio/x-";
2563 if (vmu->volgain < -.001 || vmu->volgain > .001) {
2564 create_dirpath(tmpdir, sizeof(tmpdir), vmu->context, vmu->mailbox, "tmp");
2565 snprintf(newtmp, sizeof(newtmp), "%s/XXXXXX", tmpdir);
2566 tmpfd = mkstemp(newtmp);
2567 chmod(newtmp, VOICEMAIL_FILE_MODE & ~my_umask);
2568 ast_debug(3, "newtmp: %s\n", newtmp);
2570 snprintf(tmpcmd, sizeof(tmpcmd), "sox -v %.4f %s.%s %s.%s", vmu->volgain, attach, format, newtmp, format);
2571 ast_safe_system(tmpcmd);
2573 ast_debug(3, "VOLGAIN: Stored at: %s.%s - Level: %.4f - Mailbox: %s\n", attach, format, vmu->volgain, mailbox);
2576 fprintf(p, "--%s" ENDL, bound);
2578 fprintf(p, "Content-Type: %s%s; name=\"%s\"" ENDL, ctype, format, filename);
2580 fprintf(p, "Content-Type: %s%s; name=\"%s.%s\"" ENDL, ctype, format, attach, format);
2581 fprintf(p, "Content-Transfer-Encoding: base64" ENDL);
2582 fprintf(p, "Content-Description: Voicemail sound attachment." ENDL);
2584 fprintf(p, "Content-Disposition: attachment; filename=\"%s\"" ENDL ENDL, filename);
2586 fprintf(p, "Content-Disposition: attachment; filename=\"%s.%s\"" ENDL ENDL, attach, format);
2587 snprintf(fname, sizeof(fname), "%s.%s", attach, format);
2588 base_encode(fname, p);
2590 fprintf(p, ENDL ENDL "--%s--" ENDL "." ENDL, bound);
2600 static int sendmail(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, const char *flag)
2603 char tmp[80] = "/tmp/astmail-XXXXXX";
2606 if (vmu && ast_strlen_zero(vmu->email)) {
2607 ast_log(AST_LOG_WARNING, "E-mail address missing for mailbox [%s]. E-mail will not be sent.\n", vmu->mailbox);
2610 if (!strcmp(format, "wav49"))
2612 ast_debug(3, "Attaching file '%s', format '%s', uservm is '%d', global is %d\n", attach, format, attach_user_voicemail, ast_test_flag((&globalflags), VM_ATTACH));
2613 /* Make a temporary file instead of piping directly to sendmail, in case the mail
2615 if ((p = vm_mkftemp(tmp)) == NULL) {
2616 ast_log(AST_LOG_WARNING, "Unable to launch '%s' (can't create temporary file)\n", mailcmd);
2619 make_email_file(p, srcemail, vmu, msgnum, context, mailbox, cidnum, cidname, attach, attach2, format, duration, attach_user_voicemail, chan, category, 0, flag);
2621 snprintf(tmp2, sizeof(tmp2), "( %s < %s ; rm -f %s ) &", mailcmd, tmp, tmp);
2622 ast_safe_system(tmp2);
2623 ast_debug(1, "Sent mail to %s with command '%s'\n", vmu->email, mailcmd);
2628 static int sendpage(char *srcemail, char *pager, int msgnum, char *context, char *mailbox, char *cidnum, char *cidname, int duration, struct ast_vm_user *vmu, const char *category, const char *flag)
2631 char host[MAXHOSTNAMELEN] = "";
2634 char tmp[80] = "/tmp/astmail-XXXXXX";
2635 char tmp2[PATH_MAX];
2639 if ((p = vm_mkftemp(tmp)) == NULL) {
2640 ast_log(AST_LOG_WARNING, "Unable to launch '%s' (can't create temporary file)\n", mailcmd);
2643 gethostname(host, sizeof(host)-1);
2644 if (strchr(srcemail, '@'))
2645 ast_copy_string(who, srcemail, sizeof(who));
2647 snprintf(who, sizeof(who), "%s@%s", srcemail, host);
2648 snprintf(dur, sizeof(dur), "%d:%02d", duration / 60, duration % 60);
2649 ast_strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", vmu_tm(vmu, &tm));
2650 fprintf(p, "Date: %s\n", date);
2652 if (*pagerfromstring) {
2653 struct ast_channel *ast;
2654 if ((ast = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, 0))) {
2656 int vmlen = strlen(fromstring)*3 + 200;
2657 passdata = alloca(vmlen);
2658 memset(passdata, 0, vmlen);
2659 prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category, flag);
2660 pbx_substitute_variables_helper(ast, pagerfromstring, passdata, vmlen);
2661 fprintf(p, "From: %s <%s>\n", passdata, who);
2662 ast_channel_free(ast);
2664 ast_log(AST_LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
2666 fprintf(p, "From: Asterisk PBX <%s>\n", who);
2667 fprintf(p, "To: %s\n", pager);
2669 struct ast_channel *ast;
2670 if ((ast = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, 0))) {
2672 int vmlen = strlen(pagersubject) * 3 + 200;
2673 passdata = alloca(vmlen);
2674 memset(passdata, 0, vmlen);
2675 prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category, flag);
2676 pbx_substitute_variables_helper(ast, pagersubject, passdata, vmlen);
2677 fprintf(p, "Subject: %s\n\n", passdata);
2678 ast_channel_free(ast);
2680 ast_log(AST_LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
2682 if (ast_strlen_zero(flag)) {
2683 fprintf(p, "Subject: New VM\n\n");
2685 fprintf(p, "Subject: New %s VM\n\n", flag);
2689 ast_strftime(date, sizeof(date), "%A, %B %d, %Y at %r", &tm);
2691 struct ast_channel *ast;
2692 if ((ast = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, 0))) {
2694 int vmlen = strlen(pagerbody) * 3 + 200;
2695 passdata = alloca(vmlen);
2696 memset(passdata, 0, vmlen);
2697 prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category, flag);
2698 pbx_substitute_variables_helper(ast, pagerbody, passdata, vmlen);
2699 fprintf(p, "%s\n", passdata);
2700 ast_channel_free(ast);
2702 ast_log(AST_LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
2704 fprintf(p, "New %s long %s msg in box %s\n"
2705 "from %s, on %s", dur, flag, mailbox, (cidname ? cidname : (cidnum ? cidnum : "unknown")), date);
2708 snprintf(tmp2, sizeof(tmp2), "( %s < %s ; rm -f %s ) &", mailcmd, tmp, tmp);
2709 ast_safe_system(tmp2);
2710 ast_debug(1, "Sent page to %s with command '%s'\n", pager, mailcmd);
2715 * \brief Gets the current date and time, as formatted string.
2716 * \param s The buffer to hold the output formatted date.
2717 * \param len the length of the buffer. Used to prevent buffer overflow in ast_strftime.
2719 * The date format string used is "%a %b %e %r UTC %Y".
2721 * \return zero on success, -1 on error.
2723 static int get_date(char *s, int len)
2726 struct timeval t = ast_tvnow();
2728 ast_localtime(&t, &tm, "UTC");
2730 return ast_strftime(s, len, "%a %b %e %r UTC %Y", &tm);
2733 static int invent_message(struct ast_channel *chan, char *context, char *ext, int busy, char *ecodes)
2737 char dest[PATH_MAX];
2739 snprintf(fn, sizeof(fn), "%s%s/%s/greet", VM_SPOOL_DIR, context, ext);
2741 if ((res = create_dirpath(dest, sizeof(dest), context, ext, ""))) {
2742 ast_log(AST_LOG_WARNING, "Failed to make directory(%s)\n", fn);
2746 RETRIEVE(fn, -1, ext, context);
2747 if (ast_fileexists(fn, NULL, NULL) > 0) {
2748 res = ast_stream_and_wait(chan, fn, ecodes);
2754 /* Dispose just in case */
2756 res = ast_stream_and_wait(chan, "vm-theperson", ecodes);
2759 res = ast_say_digit_str(chan, ext, ecodes, chan->language);
2763 res = ast_stream_and_wait(chan, busy ? "vm-isonphone" : "vm-isunavail", ecodes);
2767 static void free_user(struct ast_vm_user *vmu)
2769 if (!ast_test_flag(vmu, VM_ALLOCED))
2775 static void free_zone(struct vm_zone *z)
2781 * \brief Gets the name of the mailbox folder from the numeric index.
2782 * \param id The numerical index for the folder name.
2784 * When an invalid number is entered, or one that exceeds the pre-configured list of folder names, the name "tmp" is returned.
2786 * \return the String name that coresponds to this folder index.
2788 static const char *mbox(int id)
2790 static const char *msgs[] = {
2808 return (id >= 0 && id < (sizeof(msgs)/sizeof(msgs[0]))) ? msgs[id] : "Unknown";
2812 * \brief Converts a string folder name into the numerical identifier.
2813 * \param folder the string folder name to be converted to an id.
2815 * This is the opposite of the mbox() function.
2817 * \return the id that coresponds to the folder name
2819 static int folder_int(const char *folder)
2821 /* assume a NULL folder means INBOX */
2825 if (!strcasecmp(folder, imapfolder))
2827 if (!strcasecmp(folder, "INBOX"))
2830 else if (!strcasecmp(folder, "Old"))
2832 else if (!strcasecmp(folder, "Work"))
2834 else if (!strcasecmp(folder, "Family"))
2836 else if (!strcasecmp(folder, "Friends"))
2838 else if (!strcasecmp(folder, "Cust1"))
2840 else if (!strcasecmp(folder, "Cust2"))
2842 else if (!strcasecmp(folder, "Cust3"))
2844 else if (!strcasecmp(folder, "Cust4"))
2846 else if (!strcasecmp(folder, "Cust5"))
2848 else if (!strcasecmp(folder, "Deleted"))
2850 else if (!strcasecmp(folder, "Urgent"))
2852 else /*assume they meant INBOX if folder is not found otherwise*/
2858 /*! XXX \todo Fix this function to support multiple mailboxes in the intput string */
2859 static int inboxcount(const char *mailbox, int *urgentmsgs, int *newmsgs, int *oldmsgs)
2866 char tmp[PATH_MAX] = "";
2867 struct odbc_obj *obj;
2869 struct generic_prepare_struct gps = { .sql = sql, .argc = 0 };
2878 /* If no mailbox, return immediately */
2879 if (ast_strlen_zero(mailbox))
2882 ast_copy_string(tmp, mailbox, sizeof(tmp));
2884 context = strchr(tmp, '@');
2889 context = "default";
2891 obj = ast_odbc_request_obj(odbc_database, 0);
2893 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir = '%s%s/%s/%s'", odbc_table, VM_SPOOL_DIR, context, tmp, "INBOX");
2894 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
2896 ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
2897 ast_odbc_release_obj(obj);
2900 res = SQLFetch(stmt);
2901 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2902 ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
2903 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2904 ast_odbc_release_obj(obj);
2907 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
2908 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2909 ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
2910 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2911 ast_odbc_release_obj(obj);
2914 *newmsgs = atoi(rowdata);
2915 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2917 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir = '%s%s/%s/%s'", odbc_table, VM_SPOOL_DIR, context, tmp, "Old");
2918 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
2920 ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
2921 ast_odbc_release_obj(obj);
2924 res = SQLFetch(stmt);
2925 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2926 ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
2927 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2928 ast_odbc_release_obj(obj);
2931 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
2932 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2933 ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
2934 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2935 ast_odbc_release_obj(obj);
2938 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2939 *oldmsgs = atoi(rowdata);
2941 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir = '%s%s/%s/%s'", odbc_table, VM_SPOOL_DIR, context, tmp, "Urgent");
2942 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
2944 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
2945 ast_odbc_release_obj(obj);
2948 res = SQLFetch(stmt);
2949 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2950 ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
2951 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2952 ast_odbc_release_obj(obj);
2955 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
2956 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2957 ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
2958 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2959 ast_odbc_release_obj(obj);
2962 *urgentmsgs = atoi(rowdata);
2963 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2964 ast_odbc_release_obj(obj);
2967 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
2974 * \brief Gets the number of messages that exist in a mailbox folder.
2979 * This method is used when ODBC backend is used.
2980 * \return The number of messages in this mailbox folder (zero or more).
2982 static int messagecount(const char *context, const char *mailbox, const char *folder)
2984 struct odbc_obj *obj = NULL;
2987 SQLHSTMT stmt = NULL;
2990 struct generic_prepare_struct gps = { .sql = sql, .argc = 0 };
2993 /* If no mailbox, return immediately */
2994 if (ast_strlen_zero(mailbox))
2997 obj = ast_odbc_request_obj(odbc_database, 0);
2999 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir = '%s%s/%s/%s'", odbc_table, VM_SPOOL_DIR, context, mailbox, folder);
3000 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
3002 ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
3005 res = SQLFetch(stmt);
3006 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3007 ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
3008 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3011 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
3012 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3013 ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
3014 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3017 nummsgs = atoi(rowdata);
3018 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3020 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
3024 ast_odbc_release_obj(obj);
3029 * \brief Determines if the given folder has messages.
3030 * \param mailbox The @ delimited string for user@context. If no context is found, uses 'default' for the context.
3032 * This function is used when the mailbox is stored in an ODBC back end.
3033 * This invokes the messagecount(). Here we are interested in the presence of messages (> 0) only, not the actual count.
3034 * \return 1 if the folder has one or more messages. zero otherwise.
3036 static int has_voicemail(const char *mailbox, const char *folder)
3038 char tmp[256], *tmp2 = tmp, *mbox, *context;
3039 ast_copy_string(tmp, mailbox, sizeof(tmp));
3040 while ((context = mbox = strsep(&tmp2, ","))) {
3041 strsep(&context, "@");
3042 if (ast_strlen_zero(context))
3043 context = "default";
3044 if (messagecount(context, mbox, folder))
3050 #elif defined(IMAP_STORAGE)
3052 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, char *introfile, const char *flag)
3054 char *myserveremail = serveremail;
3056 char intro[PATH_MAX];
3060 char tmp[80] = "/tmp/astmail-XXXXXX";
3065 int ret; /* for better error checking */
3066 char *imapflags = NIL;
3068 /* Set urgent flag for IMAP message */
3069 if (!ast_strlen_zero(flag) && !strcmp(flag, "Urgent")) {
3070 ast_debug(3, "Setting message flag \\\\FLAGGED.\n");
3071 imapflags="\\FLAGGED";
3074 /* Attach only the first format */
3075 fmt = ast_strdupa(fmt);
3077 strsep(&stringp, "|");
3079 if (!ast_strlen_zero(vmu->serveremail))
3080 myserveremail = vmu->serveremail;
3083 make_file(fn, sizeof(fn), dir, msgnum);
3085 ast_copy_string (fn, dir, sizeof(fn));
3087 if (ast_strlen_zero(vmu->email)) {
3088 /* We need the vmu->email to be set when we call make_email_file, but
3089 * if we keep it set, a duplicate e-mail will be created. So at the end
3090 * of this function, we will revert back to an empty string if tempcopy
3093 ast_copy_string(vmu->email, vmu->imapuser, sizeof(vmu->email));
3097 if (!ast_strlen_zero(introfile)) {
3098 snprintf(intro, sizeof(intro), "%s/msgintro%04d", dir, msgnum);
3103 if (!strcmp(fmt, "wav49"))
3105 ast_debug(3, "Storing file '%s', format '%s'\n", fn, fmt);
3107 /* Make a temporary file instead of piping directly to sendmail, in case the mail
3109 if (!(p = vm_mkftemp(tmp))) {
3110 ast_log(AST_LOG_WARNING, "Unable to store '%s' (can't create temporary file)\n", fn);
3112 *(vmu->email) = '\0';