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>
66 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
76 #include <sys/types.h>
85 #ifdef USE_SYSTEM_IMAP
86 #include <imap/c-client.h>
87 #include <imap/imap4r1.h>
88 #include <imap/linkage.h>
96 #include "asterisk/lock.h"
97 #include "asterisk/file.h"
98 #include "asterisk/logger.h"
99 #include "asterisk/channel.h"
100 #include "asterisk/pbx.h"
101 #include "asterisk/options.h"
102 #include "asterisk/config.h"
103 #include "asterisk/say.h"
104 #include "asterisk/module.h"
105 #include "asterisk/adsi.h"
106 #include "asterisk/app.h"
107 #include "asterisk/manager.h"
108 #include "asterisk/dsp.h"
109 #include "asterisk/localtime.h"
110 #include "asterisk/cli.h"
111 #include "asterisk/utils.h"
112 #include "asterisk/stringfields.h"
113 #include "asterisk/smdi.h"
114 #include "asterisk/event.h"
117 #include "asterisk/res_odbc.h"
121 static char imapserver[48];
122 static char imapport[8];
123 static char imapflags[128];
124 static char imapfolder[64];
125 static char greetingfolder[64];
126 static char authuser[32];
127 static char authpassword[42];
129 static int expungeonhangup = 1;
130 static int imapgreetings = 0;
131 AST_MUTEX_DEFINE_STATIC(delimiter_lock);
132 static char delimiter = '\0';
137 /* Forward declarations for IMAP */
138 static int init_mailstream(struct vm_state *vms, int box);
139 static void write_file(char *filename, char *buffer, unsigned long len);
140 static void display_body(BODY *body, char *pfx, long i);
141 static char *get_header_by_tag(char *header, char *tag, char *buf, size_t len);
142 static void vm_imap_delete(int msgnum, struct vm_state *vms);
143 static char *get_user_by_mailbox(char *mailbox, char *buf, size_t len);
144 static struct vm_state *get_vm_state_by_imapuser(char *user, int interactive);
145 static struct vm_state *get_vm_state_by_mailbox(const char *mailbox, int interactive);
146 static void vmstate_insert(struct vm_state *vms);
147 static void vmstate_delete(struct vm_state *vms);
148 static void set_update(MAILSTREAM * stream);
149 static void init_vm_state(struct vm_state *vms);
150 static void check_msgArray(struct vm_state *vms);
151 static void copy_msgArray(struct vm_state *dst, struct vm_state *src);
152 static int save_body(BODY *body, struct vm_state *vms, char *section, char *format);
153 static int make_gsm_file(char *dest, char *imapuser, char *dir, int num);
154 static void get_mailbox_delimiter(MAILSTREAM *stream);
155 static void mm_parsequota (MAILSTREAM *stream, unsigned char *msg, QUOTALIST *pquota);
156 static void imap_mailbox_name(char *spec, struct vm_state *vms, int box, int target);
157 static int imap_store_file(char *dir, char *mailboxuser, char *mailboxcontext, int msgnum, struct ast_channel *chan, struct ast_vm_user *vmu, char *fmt, int duration, struct vm_state *vms);
158 static void update_messages_by_imapuser(const char *user, unsigned long number);
160 static int imap_remove_file (char *dir, int msgnum);
161 static int imap_retrieve_file (char *dir, int msgnum, char *mailbox, char *context);
162 static int imap_delete_old_greeting (char *dir, struct vm_state *vms);
164 struct vm_state *vms;
165 AST_LIST_ENTRY(vmstate) list;
168 static AST_LIST_HEAD_STATIC(vmstates, vmstate);
172 #define SMDI_MWI_WAIT_TIMEOUT 1000 /* 1 second */
174 #define COMMAND_TIMEOUT 5000
175 /* Don't modify these here; set your umask at runtime instead */
176 #define VOICEMAIL_DIR_MODE 0777
177 #define VOICEMAIL_FILE_MODE 0666
178 #define CHUNKSIZE 65536
180 #define VOICEMAIL_CONFIG "voicemail.conf"
181 #define ASTERISK_USERNAME "asterisk"
183 /* Define fast-forward, pause, restart, and reverse keys
184 while listening to a voicemail message - these are
185 strings, not characters */
186 #define DEFAULT_LISTEN_CONTROL_FORWARD_KEY "#"
187 #define DEFAULT_LISTEN_CONTROL_REVERSE_KEY "*"
188 #define DEFAULT_LISTEN_CONTROL_PAUSE_KEY "0"
189 #define DEFAULT_LISTEN_CONTROL_RESTART_KEY "2"
190 #define DEFAULT_LISTEN_CONTROL_STOP_KEY "13456789"
191 #define VALID_DTMF "1234567890*#" /* Yes ABCD are valid dtmf but what phones have those? */
193 /* Default mail command to mail voicemail. Change it with the
194 mailcmd= command in voicemail.conf */
195 #define SENDMAIL "/usr/sbin/sendmail -t"
197 #define INTRO "vm-intro"
200 #define MAXMSGLIMIT 9999
202 #define BASEMAXINLINE 256
203 #define BASELINELEN 72
204 #define BASEMAXINLINE 256
207 #define MAX_DATETIME_FORMAT 512
208 #define MAX_NUM_CID_CONTEXTS 10
210 #define VM_REVIEW (1 << 0)
211 #define VM_OPERATOR (1 << 1)
212 #define VM_SAYCID (1 << 2)
213 #define VM_SVMAIL (1 << 3)
214 #define VM_ENVELOPE (1 << 4)
215 #define VM_SAYDURATION (1 << 5)
216 #define VM_SKIPAFTERCMD (1 << 6)
217 #define VM_FORCENAME (1 << 7) /*!< Have new users record their name */
218 #define VM_FORCEGREET (1 << 8) /*!< Have new users record their greetings */
219 #define VM_PBXSKIP (1 << 9)
220 #define VM_DIRECFORWARD (1 << 10) /*!< directory_forward */
221 #define VM_ATTACH (1 << 11)
222 #define VM_DELETE (1 << 12)
223 #define VM_ALLOCED (1 << 13)
224 #define VM_SEARCH (1 << 14)
225 #define VM_TEMPGREETWARN (1 << 15) /*!< Remind user tempgreeting is set */
226 #define ERROR_LOCK_PATH -100
239 OPT_SILENT = (1 << 0),
240 OPT_BUSY_GREETING = (1 << 1),
241 OPT_UNAVAIL_GREETING = (1 << 2),
242 OPT_RECORDGAIN = (1 << 3),
243 OPT_PREPEND_MAILBOX = (1 << 4),
244 OPT_AUTOPLAY = (1 << 6),
248 OPT_ARG_RECORDGAIN = 0,
249 OPT_ARG_PLAYFOLDER = 1,
250 /* This *must* be the last value in this enum! */
251 OPT_ARG_ARRAY_SIZE = 2,
254 AST_APP_OPTIONS(vm_app_options, {
255 AST_APP_OPTION('s', OPT_SILENT),
256 AST_APP_OPTION('b', OPT_BUSY_GREETING),
257 AST_APP_OPTION('u', OPT_UNAVAIL_GREETING),
258 AST_APP_OPTION_ARG('g', OPT_RECORDGAIN, OPT_ARG_RECORDGAIN),
259 AST_APP_OPTION('p', OPT_PREPEND_MAILBOX),
260 AST_APP_OPTION_ARG('a', OPT_AUTOPLAY, OPT_ARG_PLAYFOLDER),
263 static int load_config(int reload);
265 /*! \page vmlang Voicemail Language Syntaxes Supported
267 \par Syntaxes supported, not really language codes.
274 \arg \b pt - Portuguese
275 \arg \b pt_BR - Portuguese (Brazil)
277 \arg \b no - Norwegian
279 \arg \b tw - Chinese (Taiwan)
280 \arg \b ua - Ukrainian
282 German requires the following additional soundfile:
283 \arg \b 1F einE (feminine)
285 Spanish requires the following additional soundfile:
286 \arg \b 1M un (masculine)
288 Dutch, Portuguese & Spanish require the following additional soundfiles:
289 \arg \b vm-INBOXs singular of 'new'
290 \arg \b vm-Olds singular of 'old/heard/read'
293 \arg \b vm-INBOX nieuwe (nl)
294 \arg \b vm-Old oude (nl)
297 \arg \b vm-new-a 'new', feminine singular accusative
298 \arg \b vm-new-e 'new', feminine plural accusative
299 \arg \b vm-new-ych 'new', feminine plural genitive
300 \arg \b vm-old-a 'old', feminine singular accusative
301 \arg \b vm-old-e 'old', feminine plural accusative
302 \arg \b vm-old-ych 'old', feminine plural genitive
303 \arg \b digits/1-a 'one', not always same as 'digits/1'
304 \arg \b digits/2-ie 'two', not always same as 'digits/2'
307 \arg \b vm-nytt singular of 'new'
308 \arg \b vm-nya plural of 'new'
309 \arg \b vm-gammalt singular of 'old'
310 \arg \b vm-gamla plural of 'old'
311 \arg \b digits/ett 'one', not always same as 'digits/1'
314 \arg \b vm-ny singular of 'new'
315 \arg \b vm-nye plural of 'new'
316 \arg \b vm-gammel singular of 'old'
317 \arg \b vm-gamle plural of 'old'
325 Ukrainian requires the following additional soundfile:
326 \arg \b vm-nove 'nove'
327 \arg \b vm-stare 'stare'
328 \arg \b digits/ua/1e 'odne'
330 Italian requires the following additional soundfile:
334 \arg \b vm-nuovi new plural
335 \arg \b vm-vecchio old
336 \arg \b vm-vecchi old plural
338 Chinese (Taiwan) requires the following additional soundfile:
339 \arg \b vm-tong A class-word for call (tong1)
340 \arg \b vm-ri A class-word for day (ri4)
341 \arg \b vm-you You (ni3)
342 \arg \b vm-haveno Have no (mei2 you3)
343 \arg \b vm-have Have (you3)
344 \arg \b vm-listen To listen (yao4 ting1)
347 \note Don't use vm-INBOX or vm-Old, because they are the name of the INBOX and Old folders,
348 spelled among others when you have to change folder. For the above reasons, vm-INBOX
349 and vm-Old are spelled plural, to make them sound more as folder name than an adjective.
358 unsigned char iobuf[BASEMAXINLINE];
361 /*! Structure for linked list of users
362 * Use ast_vm_user_destroy() to free one of these structures. */
364 char context[AST_MAX_CONTEXT]; /*!< Voicemail context */
365 char mailbox[AST_MAX_EXTENSION]; /*!< Mailbox id, unique within vm context */
366 char password[80]; /*!< Secret pin code, numbers only */
367 char fullname[80]; /*!< Full name, for directory app */
368 char email[80]; /*!< E-mail address */
369 char pager[80]; /*!< E-mail address to pager (no attachment) */
370 char serveremail[80]; /*!< From: Mail address */
371 char mailcmd[160]; /*!< Configurable mail command */
372 char language[MAX_LANGUAGE]; /*!< Config: Language setting */
373 char zonetag[80]; /*!< Time zone */
376 char uniqueid[20]; /*!< Unique integer identifier */
378 char attachfmt[20]; /*!< Attachment format */
379 unsigned int flags; /*!< VM_ flags */
381 int maxmsg; /*!< Maximum number of msgs per folder for this mailbox */
382 int maxsecs; /*!< Maximum number of seconds per message for this mailbox */
384 char imapuser[80]; /*!< IMAP server login */
385 char imappassword[80]; /*!< IMAP server password if authpassword not defined */
387 double volgain; /*!< Volume gain for voicemails sent via email */
388 AST_LIST_ENTRY(ast_vm_user) list;
391 /*! Voicemail time zones */
393 AST_LIST_ENTRY(vm_zone) list;
396 char msg_format[512];
399 /*! Voicemail mailbox state */
403 char curdir[PATH_MAX];
404 char vmbox[PATH_MAX];
416 int updated; /*!< decremented on each mail check until 1 -allows delay */
418 MAILSTREAM *mailstream;
420 char imapuser[80]; /*!< IMAP server login */
422 unsigned int quota_limit;
423 unsigned int quota_usage;
424 struct vm_state *persist_vms;
430 static char odbc_database[80];
431 static char odbc_table[80];
432 #define RETRIEVE(a,b,c,d) retrieve_file(a,b)
433 #define DISPOSE(a,b) remove_file(a,b)
434 #define STORE(a,b,c,d,e,f,g,h,i) store_file(a,b,c,d)
435 #define EXISTS(a,b,c,d) (message_exists(a,b))
436 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(a,b,c,d,e,f))
437 #define COPY(a,b,c,d,e,f,g,h) (copy_file(a,b,c,d,e,f))
438 #define DELETE(a,b,c) (delete_file(a,b))
441 #define RETRIEVE(a,b,c,d) (imap_retrieve_file(a,b,c,d ))
442 #define DISPOSE(a,b) (imap_remove_file(a,b))
443 #define STORE(a,b,c,d,e,f,g,h,i) (imap_store_file(a,b,c,d,e,f,g,h,i))
444 #define EXISTS(a,b,c,d) (ast_fileexists(c,NULL,d) > 0)
445 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(g,h));
446 #define COPY(a,b,c,d,e,f,g,h) (copy_file(g,h));
447 #define IMAP_DELETE(a,b,c,d) (vm_imap_delete(b,d))
448 #define DELETE(a,b,c) (vm_delete(c))
450 #define RETRIEVE(a,b,c,d)
452 #define STORE(a,b,c,d,e,f,g,h,i)
453 #define EXISTS(a,b,c,d) (ast_fileexists(c,NULL,d) > 0)
454 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(g,h));
455 #define COPY(a,b,c,d,e,f,g,h) (copy_file(g,h));
456 #define DELETE(a,b,c) (vm_delete(c))
460 static char VM_SPOOL_DIR[PATH_MAX];
462 static char ext_pass_cmd[128];
466 #define PWDCHANGE_INTERNAL (1 << 1)
467 #define PWDCHANGE_EXTERNAL (1 << 2)
468 static int pwdchange = PWDCHANGE_INTERNAL;
471 #define tdesc "Comedian Mail (Voicemail System) with ODBC Storage"
474 # define tdesc "Comedian Mail (Voicemail System) with IMAP Storage"
476 # define tdesc "Comedian Mail (Voicemail System)"
480 static char userscontext[AST_MAX_EXTENSION] = "default";
482 static char *addesc = "Comedian Mail";
484 static char *synopsis_vm = "Leave a Voicemail message";
486 static char *descrip_vm =
487 " VoiceMail(mailbox[@context][&mailbox[@context]][...][,options]): This\n"
488 "application allows the calling party to leave a message for the specified\n"
489 "list of mailboxes. When multiple mailboxes are specified, the greeting will\n"
490 "be taken from the first mailbox specified. Dialplan execution will stop if the\n"
491 "specified mailbox does not exist.\n"
492 " The Voicemail application will exit if any of the following DTMF digits are\n"
494 " 0 - Jump to the 'o' extension in the current dialplan context.\n"
495 " * - Jump to the 'a' extension in the current dialplan context.\n"
496 " This application will set the following channel variable upon completion:\n"
497 " VMSTATUS - This indicates the status of the execution of the VoiceMail\n"
498 " application. The possible values are:\n"
499 " SUCCESS | USEREXIT | FAILED\n\n"
501 " b - Play the 'busy' greeting to the calling party.\n"
502 " g(#) - Use the specified amount of gain when recording the voicemail\n"
503 " message. The units are whole-number decibels (dB).\n"
504 " s - Skip the playback of instructions for leaving a message to the\n"
506 " u - Play the 'unavailable' greeting.\n";
508 static char *synopsis_vmain = "Check Voicemail messages";
510 static char *descrip_vmain =
511 " VoiceMailMain([mailbox][@context][,options]): This application allows the\n"
512 "calling party to check voicemail messages. A specific mailbox, and optional\n"
513 "corresponding context, may be specified. If a mailbox is not provided, the\n"
514 "calling party will be prompted to enter one. If a context is not specified,\n"
515 "the 'default' context will be used.\n\n"
517 " p - Consider the mailbox parameter as a prefix to the mailbox that\n"
518 " is entered by the caller.\n"
519 " g(#) - Use the specified amount of gain when recording a voicemail\n"
520 " message. The units are whole-number decibels (dB).\n"
521 " s - Skip checking the passcode for the mailbox.\n"
522 " a(#) - Skip folder prompt and go directly to folder specified.\n"
523 " Defaults to INBOX\n";
525 static char *synopsis_vm_box_exists =
526 "Check to see if Voicemail mailbox exists";
528 static char *descrip_vm_box_exists =
529 " MailboxExists(mailbox[@context][,options]): Check to see if the specified\n"
530 "mailbox exists. If no voicemail context is specified, the 'default' context\n"
532 " This application will set the following channel variable upon completion:\n"
533 " VMBOXEXISTSSTATUS - This will contain the status of the execution of the\n"
534 " MailboxExists application. Possible values include:\n"
535 " SUCCESS | FAILED\n\n"
536 " Options: (none)\n";
538 static char *synopsis_vmauthenticate = "Authenticate with Voicemail passwords";
540 static char *descrip_vmauthenticate =
541 " VMAuthenticate([mailbox][@context][,options]): This application behaves the\n"
542 "same way as the Authenticate application, but the passwords are taken from\n"
544 " If the mailbox is specified, only that mailbox's password will be considered\n"
545 "valid. If the mailbox is not specified, the channel variable AUTH_MAILBOX will\n"
546 "be set with the authenticated mailbox.\n\n"
548 " s - Skip playing the initial prompts.\n";
550 /* Leave a message */
551 static char *app = "VoiceMail";
553 /* Check mail, control, etc */
554 static char *app2 = "VoiceMailMain";
556 static char *app3 = "MailboxExists";
557 static char *app4 = "VMAuthenticate";
559 static AST_LIST_HEAD_STATIC(users, ast_vm_user);
560 static AST_LIST_HEAD_STATIC(zones, vm_zone);
561 static int maxsilence;
563 static int silencethreshold = 128;
564 static char serveremail[80];
565 static char mailcmd[160]; /* Configurable mail cmd */
566 static char externnotify[160];
567 static struct ast_smdi_interface *smdi_iface = NULL;
568 static char vmfmts[80];
569 static double volgain;
570 static int vmminsecs;
571 static int vmmaxsecs;
574 static int maxlogins;
576 /*! Poll mailboxes for changes since there is something external to
577 * app_voicemail that may change them. */
578 static unsigned int poll_mailboxes;
580 /*! Polling frequency */
581 static unsigned int poll_freq;
582 /*! By default, poll every 30 seconds */
583 #define DEFAULT_POLL_FREQ 30
585 AST_MUTEX_DEFINE_STATIC(poll_lock);
586 static ast_cond_t poll_cond = PTHREAD_COND_INITIALIZER;
587 static pthread_t poll_thread = AST_PTHREADT_NULL;
588 static unsigned char poll_thread_run;
590 /*! Subscription to ... MWI event subscriptions */
591 static struct ast_event_sub *mwi_sub_sub;
592 /*! Subscription to ... MWI event un-subscriptions */
593 static struct ast_event_sub *mwi_unsub_sub;
596 * \brief An MWI subscription
598 * This is so we can keep track of which mailboxes are subscribed to.
599 * This way, we know which mailboxes to poll when the pollmailboxes
600 * option is being used.
603 AST_RWLIST_ENTRY(mwi_sub) entry;
610 static AST_RWLIST_HEAD_STATIC(mwi_subs, mwi_sub);
612 /* custom audio control prompts for voicemail playback */
613 static char listen_control_forward_key[12];
614 static char listen_control_reverse_key[12];
615 static char listen_control_pause_key[12];
616 static char listen_control_restart_key[12];
617 static char listen_control_stop_key[12];
619 /* custom password sounds */
620 static char vm_password[80] = "vm-password";
621 static char vm_newpassword[80] = "vm-newpassword";
622 static char vm_passchanged[80] = "vm-passchanged";
623 static char vm_reenterpassword[80] = "vm-reenterpassword";
624 static char vm_mismatch[80] = "vm-mismatch";
626 static struct ast_flags globalflags = {0};
628 static int saydurationminfo;
630 static char dialcontext[AST_MAX_CONTEXT] = "";
631 static char callcontext[AST_MAX_CONTEXT] = "";
632 static char exitcontext[AST_MAX_CONTEXT] = "";
634 static char cidinternalcontexts[MAX_NUM_CID_CONTEXTS][64];
637 static char *emailbody = NULL;
638 static char *emailsubject = NULL;
639 static char *pagerbody = NULL;
640 static char *pagersubject = NULL;
641 static char fromstring[100];
642 static char pagerfromstring[100];
643 static char emailtitle[100];
644 static char charset[32] = "ISO-8859-1";
646 static unsigned char adsifdn[4] = "\x00\x00\x00\x0F";
647 static unsigned char adsisec[4] = "\x9B\xDB\xF7\xAC";
648 static int adsiver = 1;
649 static char emaildateformat[32] = "%A, %B %d, %Y at %r";
651 /* Forward declarations - generic */
652 static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu,int box);
653 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);
654 static int dialout(struct ast_channel *chan, struct ast_vm_user *vmu, char *num, char *outgoing_context);
655 static int play_record_review(struct ast_channel *chan, char *playfile, char *recordfile, int maxtime,
656 char *fmt, int outsidecaller, struct ast_vm_user *vmu, int *duration, const char *unlockdir,
657 signed char record_gain, struct vm_state *vms);
658 static int vm_tempgreeting(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, char *fmtc, signed char record_gain);
659 static int vm_play_folder_name(struct ast_channel *chan, char *mbox);
660 static int notify_new_message(struct ast_channel *chan, struct ast_vm_user *vmu, int msgnum, long duration, char *fmt, char *cidnum, char *cidname);
661 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 *format, int duration, int attach_user_voicemail, struct ast_channel *chan, const char *category, int imap);
662 static void apply_options(struct ast_vm_user *vmu, const char *options);
663 static int is_valid_dtmf(const char *key);
665 #if !(defined(ODBC_STORAGE) || defined(IMAP_STORAGE))
666 static int __has_voicemail(const char *context, const char *mailbox, const char *folder, int shortcircuit);
671 static void populate_defaults(struct ast_vm_user *vmu)
673 ast_copy_flags(vmu, (&globalflags), AST_FLAGS_ALL);
674 if (saydurationminfo)
675 vmu->saydurationm = saydurationminfo;
676 ast_copy_string(vmu->callback, callcontext, sizeof(vmu->callback));
677 ast_copy_string(vmu->dialout, dialcontext, sizeof(vmu->dialout));
678 ast_copy_string(vmu->exit, exitcontext, sizeof(vmu->exit));
680 vmu->maxsecs = vmmaxsecs;
682 vmu->maxmsg = maxmsg;
683 vmu->volgain = volgain;
686 static void apply_option(struct ast_vm_user *vmu, const char *var, const char *value)
689 if (!strcasecmp(var, "attach")) {
690 ast_set2_flag(vmu, ast_true(value), VM_ATTACH);
691 } else if (!strcasecmp(var, "attachfmt")) {
692 ast_copy_string(vmu->attachfmt, value, sizeof(vmu->attachfmt));
693 } else if (!strcasecmp(var, "serveremail")) {
694 ast_copy_string(vmu->serveremail, value, sizeof(vmu->serveremail));
695 } else if (!strcasecmp(var, "language")) {
696 ast_copy_string(vmu->language, value, sizeof(vmu->language));
697 } else if (!strcasecmp(var, "tz")) {
698 ast_copy_string(vmu->zonetag, value, sizeof(vmu->zonetag));
700 } else if (!strcasecmp(var, "imapuser")) {
701 ast_copy_string(vmu->imapuser, value, sizeof(vmu->imapuser));
702 } else if (!strcasecmp(var, "imappassword")) {
703 ast_copy_string(vmu->imappassword, value, sizeof(vmu->imappassword));
705 } else if (!strcasecmp(var, "delete") || !strcasecmp(var, "deletevoicemail")) {
706 ast_set2_flag(vmu, ast_true(value), VM_DELETE);
707 } else if (!strcasecmp(var, "saycid")){
708 ast_set2_flag(vmu, ast_true(value), VM_SAYCID);
709 } else if (!strcasecmp(var,"sendvoicemail")){
710 ast_set2_flag(vmu, ast_true(value), VM_SVMAIL);
711 } else if (!strcasecmp(var, "review")){
712 ast_set2_flag(vmu, ast_true(value), VM_REVIEW);
713 } else if (!strcasecmp(var, "tempgreetwarn")){
714 ast_set2_flag(vmu, ast_true(value), VM_TEMPGREETWARN);
715 } else if (!strcasecmp(var, "operator")){
716 ast_set2_flag(vmu, ast_true(value), VM_OPERATOR);
717 } else if (!strcasecmp(var, "envelope")){
718 ast_set2_flag(vmu, ast_true(value), VM_ENVELOPE);
719 } else if (!strcasecmp(var, "sayduration")){
720 ast_set2_flag(vmu, ast_true(value), VM_SAYDURATION);
721 } else if (!strcasecmp(var, "saydurationm")){
722 if (sscanf(value, "%d", &x) == 1) {
723 vmu->saydurationm = x;
725 ast_log(LOG_WARNING, "Invalid min duration for say duration\n");
727 } else if (!strcasecmp(var, "forcename")){
728 ast_set2_flag(vmu, ast_true(value), VM_FORCENAME);
729 } else if (!strcasecmp(var, "forcegreetings")){
730 ast_set2_flag(vmu, ast_true(value), VM_FORCEGREET);
731 } else if (!strcasecmp(var, "callback")) {
732 ast_copy_string(vmu->callback, value, sizeof(vmu->callback));
733 } else if (!strcasecmp(var, "dialout")) {
734 ast_copy_string(vmu->dialout, value, sizeof(vmu->dialout));
735 } else if (!strcasecmp(var, "exitcontext")) {
736 ast_copy_string(vmu->exit, value, sizeof(vmu->exit));
737 } else if (!strcasecmp(var, "maxmessage")) {
738 if (vmu->maxsecs <= 0) {
739 ast_log(LOG_WARNING, "Invalid max message length of %s. Using global value %i\n", value, vmmaxsecs);
740 vmu->maxsecs = vmmaxsecs;
742 vmu->maxsecs = atoi(value);
744 } else if (!strcasecmp(var, "maxmsg")) {
745 vmu->maxmsg = atoi(value);
746 if (vmu->maxmsg <= 0) {
747 ast_log(LOG_WARNING, "Invalid number of messages per folder maxmsg=%s. Using default value %i\n", value, MAXMSG);
748 vmu->maxmsg = MAXMSG;
749 } else if (vmu->maxmsg > MAXMSGLIMIT) {
750 ast_log(LOG_WARNING, "Maximum number of messages per folder is %i. Cannot accept value maxmsg=%s\n", MAXMSGLIMIT, value);
751 vmu->maxmsg = MAXMSGLIMIT;
753 } else if (!strcasecmp(var, "volgain")) {
754 sscanf(value, "%lf", &vmu->volgain);
755 } else if (!strcasecmp(var, "options")) {
756 apply_options(vmu, value);
760 static int change_password_realtime(struct ast_vm_user *vmu, const char *password)
763 if (!ast_strlen_zero(vmu->uniqueid)) {
764 res = ast_update_realtime("voicemail", "uniqueid", vmu->uniqueid, "password", password, NULL);
766 ast_copy_string(vmu->password, password, sizeof(vmu->password));
776 static void apply_options(struct ast_vm_user *vmu, const char *options)
777 { /* Destructively Parse options and apply */
781 stringp = ast_strdupa(options);
782 while ((s = strsep(&stringp, "|"))) {
784 if ((var = strsep(&value, "=")) && value) {
785 apply_option(vmu, var, value);
790 static void apply_options_full(struct ast_vm_user *retval, struct ast_variable *var)
792 struct ast_variable *tmp;
795 if (!strcasecmp(tmp->name, "vmsecret")) {
796 ast_copy_string(retval->password, tmp->value, sizeof(retval->password));
797 } else if (!strcasecmp(tmp->name, "secret") || !strcasecmp(tmp->name, "password")) { /* don't overwrite vmsecret if it exists */
798 if (ast_strlen_zero(retval->password))
799 ast_copy_string(retval->password, tmp->value, sizeof(retval->password));
800 } else if (!strcasecmp(tmp->name, "uniqueid")) {
801 ast_copy_string(retval->uniqueid, tmp->value, sizeof(retval->uniqueid));
802 } else if (!strcasecmp(tmp->name, "pager")) {
803 ast_copy_string(retval->pager, tmp->value, sizeof(retval->pager));
804 } else if (!strcasecmp(tmp->name, "email")) {
805 ast_copy_string(retval->email, tmp->value, sizeof(retval->email));
806 } else if (!strcasecmp(tmp->name, "fullname")) {
807 ast_copy_string(retval->fullname, tmp->value, sizeof(retval->fullname));
808 } else if (!strcasecmp(tmp->name, "context")) {
809 ast_copy_string(retval->context, tmp->value, sizeof(retval->context));
811 } else if (!strcasecmp(tmp->name, "imapuser")) {
812 ast_copy_string(retval->imapuser, tmp->value, sizeof(retval->imapuser));
813 } else if (!strcasecmp(tmp->name, "imappassword")) {
814 ast_copy_string(retval->imappassword, tmp->value, sizeof(retval->imappassword));
817 apply_option(retval, tmp->name, tmp->value);
822 static int is_valid_dtmf(const char *key)
825 char *local_key = ast_strdupa(key);
827 for(i = 0; i < strlen(key); ++i) {
828 if(!strchr(VALID_DTMF, *local_key)) {
829 ast_log(LOG_WARNING, "Invalid DTMF key \"%c\" used in voicemail configuration file\n", *local_key);
837 static struct ast_vm_user *find_user_realtime(struct ast_vm_user *ivm, const char *context, const char *mailbox)
839 struct ast_variable *var;
840 struct ast_vm_user *retval;
842 if ((retval = (ivm ? ivm : ast_calloc(1, sizeof(*retval))))) {
844 ast_set_flag(retval, VM_ALLOCED);
846 memset(retval, 0, sizeof(*retval));
848 ast_copy_string(retval->mailbox, mailbox, sizeof(retval->mailbox));
849 populate_defaults(retval);
850 if (!context && ast_test_flag((&globalflags), VM_SEARCH))
851 var = ast_load_realtime("voicemail", "mailbox", mailbox, NULL);
853 var = ast_load_realtime("voicemail", "mailbox", mailbox, "context", context, NULL);
855 apply_options_full(retval, var);
856 ast_variables_destroy(var);
866 static struct ast_vm_user *find_user(struct ast_vm_user *ivm, const char *context, const char *mailbox)
868 /* This function could be made to generate one from a database, too */
869 struct ast_vm_user *vmu=NULL, *cur;
870 AST_LIST_LOCK(&users);
872 if (!context && !ast_test_flag((&globalflags), VM_SEARCH))
875 AST_LIST_TRAVERSE(&users, cur, list) {
876 if (ast_test_flag((&globalflags), VM_SEARCH) && !strcasecmp(mailbox, cur->mailbox))
878 if (context && (!strcasecmp(context, cur->context)) && (!strcasecmp(mailbox, cur->mailbox)))
882 /* Make a copy, so that on a reload, we have no race */
883 if ((vmu = (ivm ? ivm : ast_malloc(sizeof(*vmu))))) {
884 memcpy(vmu, cur, sizeof(*vmu));
885 ast_set2_flag(vmu, !ivm, VM_ALLOCED);
886 AST_LIST_NEXT(vmu, list) = NULL;
889 vmu = find_user_realtime(ivm, context, mailbox);
890 AST_LIST_UNLOCK(&users);
894 static int reset_user_pw(const char *context, const char *mailbox, const char *newpass)
896 /* This function could be made to generate one from a database, too */
897 struct ast_vm_user *cur;
899 AST_LIST_LOCK(&users);
900 AST_LIST_TRAVERSE(&users, cur, list) {
901 if ((!context || !strcasecmp(context, cur->context)) &&
902 (!strcasecmp(mailbox, cur->mailbox)))
906 ast_copy_string(cur->password, newpass, sizeof(cur->password));
909 AST_LIST_UNLOCK(&users);
913 static void vm_change_password(struct ast_vm_user *vmu, const char *newpassword)
915 struct ast_config *cfg=NULL;
916 struct ast_variable *var=NULL;
917 struct ast_category *cat=NULL;
918 char *category=NULL, *value=NULL, *new=NULL;
919 const char *tmp=NULL;
920 struct ast_flags config_flags = { CONFIG_FLAG_WITHCOMMENTS };
922 if (!change_password_realtime(vmu, newpassword))
925 /* check voicemail.conf */
926 if ((cfg = ast_config_load(VOICEMAIL_CONFIG, config_flags))) {
927 while ((category = ast_category_browse(cfg, category))) {
928 if (!strcasecmp(category, vmu->context)) {
929 if (!(tmp = ast_variable_retrieve(cfg, category, vmu->mailbox))) {
930 ast_log(LOG_WARNING, "We could not find the mailbox.\n");
933 value = strstr(tmp,",");
935 ast_log(LOG_WARNING, "variable has bad format.\n");
938 new = alloca((strlen(value)+strlen(newpassword)+1));
939 sprintf(new,"%s%s", newpassword, value);
940 if (!(cat = ast_category_get(cfg, category))) {
941 ast_log(LOG_WARNING, "Failed to get category structure.\n");
944 ast_variable_update(cat, vmu->mailbox, new, NULL, 0);
947 /* save the results */
948 reset_user_pw(vmu->context, vmu->mailbox, newpassword);
949 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
950 config_text_file_save(VOICEMAIL_CONFIG, cfg, "AppVoicemail");
954 /* check users.conf and update the password stored for the mailbox*/
955 /* if no vmsecret entry exists create one. */
956 if ((cfg = ast_config_load("users.conf", config_flags))) {
957 ast_debug(4, "we are looking for %s\n", vmu->mailbox);
958 while ((category = ast_category_browse(cfg, category))) {
959 ast_debug(4, "users.conf: %s\n", category);
960 if (!strcasecmp(category, vmu->mailbox)) {
961 if (!(tmp = ast_variable_retrieve(cfg, category, "vmsecret"))) {
962 ast_debug(3, "looks like we need to make vmsecret!\n");
963 var = ast_variable_new("vmsecret", newpassword);
965 new = alloca(strlen(newpassword)+1);
966 sprintf(new, "%s", newpassword);
967 if (!(cat = ast_category_get(cfg, category))) {
968 ast_debug(4, "failed to get category!\n");
972 ast_variable_update(cat, "vmsecret", new, NULL, 0);
974 ast_variable_append(cat, var);
977 /* save the results and clean things up */
978 reset_user_pw(vmu->context, vmu->mailbox, newpassword);
979 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
980 config_text_file_save("users.conf", cfg, "AppVoicemail");
984 static void vm_change_password_shell(struct ast_vm_user *vmu, char *newpassword)
987 snprintf(buf,255,"%s %s %s %s",ext_pass_cmd,vmu->context,vmu->mailbox,newpassword);
988 if (!ast_safe_system(buf))
989 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
992 static int make_dir(char *dest, int len, const char *context, const char *ext, const char *folder)
994 return snprintf(dest, len, "%s%s/%s/%s", VM_SPOOL_DIR, context, ext, folder);
998 static int make_gsm_file(char *dest, char *imapuser, char *dir, int num)
1001 if ((res = ast_mkdir(dir, 01777))) {
1002 ast_log(LOG_WARNING, "ast_mkdir '%s' failed: %s\n", dir, strerror(res));
1003 return sprintf(dest, "%s/msg%04d", dir, num);
1005 /* return sprintf(dest, "%s/s/msg%04d", dir, imapuser, num); */
1006 return sprintf(dest, "%s/msg%04d", dir, num);
1009 static void vm_imap_delete(int msgnum, struct vm_state *vms)
1011 unsigned long messageNum = 0;
1014 /* find real message number based on msgnum */
1015 /* this may be an index into vms->msgArray based on the msgnum. */
1017 messageNum = vms->msgArray[msgnum];
1018 if (messageNum == 0) {
1019 ast_log(LOG_WARNING, "msgnum %d, mailbox message %lu is zero.\n",msgnum,messageNum);
1022 ast_debug(3, "deleting msgnum %d, which is mailbox message %lu\n",msgnum,messageNum);
1023 /* delete message */
1024 sprintf (arg,"%lu",messageNum);
1025 mail_setflag (vms->mailstream,arg,"\\DELETED");
1029 static int make_file(char *dest, int len, char *dir, int num)
1031 return snprintf(dest, len, "%s/msg%04d", dir, num);
1034 /*! \brief basically mkdir -p $dest/$context/$ext/$folder
1035 * \param dest String. base directory.
1036 * \param len Length of dest.
1037 * \param context String. Ignored if is null or empty string.
1038 * \param ext String. Ignored if is null or empty string.
1039 * \param folder String. Ignored if is null or empty string.
1040 * \return -1 on failure, 0 on success.
1042 static int create_dirpath(char *dest, int len, const char *context, const char *ext, const char *folder)
1044 mode_t mode = VOICEMAIL_DIR_MODE;
1047 make_dir(dest, len, context, ext, folder);
1048 if ((res = ast_mkdir(dest, mode))) {
1049 ast_log(LOG_WARNING, "ast_mkdir '%s' failed: %s\n", dest, strerror(res));
1055 /*! \brief Lock file path
1056 only return failure if ast_lock_path returns 'timeout',
1057 not if the path does not exist or any other reason
1059 static int vm_lock_path(const char *path)
1061 switch (ast_lock_path(path)) {
1062 case AST_LOCK_TIMEOUT:
1071 struct generic_prepare_struct {
1077 static SQLHSTMT generic_prepare(struct odbc_obj *obj, void *data)
1079 struct generic_prepare_struct *gps = data;
1083 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
1084 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1085 ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
1088 res = SQLPrepare(stmt, (unsigned char *)gps->sql, SQL_NTS);
1089 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1090 ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", gps->sql);
1091 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
1094 for (i = 0; i < gps->argc; i++)
1095 SQLBindParameter(stmt, i + 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(gps->argv[i]), 0, gps->argv[i], 0, NULL);
1100 static int retrieve_file(char *dir, int msgnum)
1106 void *fdm = MAP_FAILED;
1107 SQLSMALLINT colcount=0;
1114 SQLSMALLINT datatype;
1115 SQLSMALLINT decimaldigits;
1116 SQLSMALLINT nullable;
1122 char full_fn[PATH_MAX];
1124 char *argv[] = { dir, msgnums };
1125 struct generic_prepare_struct gps = { .sql = sql, .argc = 2, .argv = argv };
1127 struct odbc_obj *obj;
1128 obj = ast_odbc_request_obj(odbc_database, 0);
1130 ast_copy_string(fmt, vmfmts, sizeof(fmt));
1131 c = strchr(fmt, '|');
1134 if (!strcasecmp(fmt, "wav49"))
1136 snprintf(msgnums, sizeof(msgnums),"%d", msgnum);
1138 make_file(fn, sizeof(fn), dir, msgnum);
1140 ast_copy_string(fn, dir, sizeof(fn));
1141 snprintf(full_fn, sizeof(full_fn), "%s.txt", fn);
1143 if (!(f = fopen(full_fn, "w+"))) {
1144 ast_log(LOG_WARNING, "Failed to open/create '%s'\n", full_fn);
1148 snprintf(full_fn, sizeof(full_fn), "%s.%s", fn, fmt);
1149 snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE dir=? AND msgnum=?",odbc_table);
1150 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
1152 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
1153 ast_odbc_release_obj(obj);
1156 res = SQLFetch(stmt);
1157 if (res == SQL_NO_DATA) {
1158 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1159 ast_odbc_release_obj(obj);
1162 else if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1163 ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
1164 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1165 ast_odbc_release_obj(obj);
1168 fd = open(full_fn, O_RDWR | O_CREAT | O_TRUNC, VOICEMAIL_FILE_MODE);
1170 ast_log(LOG_WARNING, "Failed to write '%s': %s\n", full_fn, strerror(errno));
1171 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1172 ast_odbc_release_obj(obj);
1175 res = SQLNumResultCols(stmt, &colcount);
1176 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1177 ast_log(LOG_WARNING, "SQL Column Count error!\n[%s]\n\n", sql);
1178 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1179 ast_odbc_release_obj(obj);
1183 fprintf(f, "[message]\n");
1184 for (x=0;x<colcount;x++) {
1186 collen = sizeof(coltitle);
1187 res = SQLDescribeCol(stmt, x + 1, (unsigned char *)coltitle, sizeof(coltitle), &collen,
1188 &datatype, &colsize, &decimaldigits, &nullable);
1189 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1190 ast_log(LOG_WARNING, "SQL Describe Column error!\n[%s]\n\n", sql);
1191 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1192 ast_odbc_release_obj(obj);
1195 if (!strcasecmp(coltitle, "recording")) {
1197 res = SQLGetData(stmt, x + 1, SQL_BINARY, rowdata, 0, &colsize2);
1201 lseek(fd, fdlen - 1, SEEK_SET);
1202 if (write(fd, tmp, 1) != 1) {
1207 /* Read out in small chunks */
1208 for (offset = 0; offset < colsize2; offset += CHUNKSIZE) {
1209 if ((fdm = mmap(NULL, CHUNKSIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, offset)) == MAP_FAILED) {
1210 ast_log(LOG_WARNING, "Could not mmap the output file: %s (%d)\n", strerror(errno), errno);
1211 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
1212 ast_odbc_release_obj(obj);
1215 res = SQLGetData(stmt, x + 1, SQL_BINARY, fdm, CHUNKSIZE, NULL);
1216 munmap(fdm, CHUNKSIZE);
1217 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1218 ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
1220 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
1221 ast_odbc_release_obj(obj);
1226 truncate(full_fn, fdlen);
1229 res = SQLGetData(stmt, x + 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
1230 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1231 ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
1232 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1233 ast_odbc_release_obj(obj);
1236 if (strcasecmp(coltitle, "msgnum") && strcasecmp(coltitle, "dir") && f)
1237 fprintf(f, "%s=%s\n", coltitle, rowdata);
1240 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1241 ast_odbc_release_obj(obj);
1243 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
1252 static int remove_file(char *dir, int msgnum)
1255 char full_fn[PATH_MAX];
1259 snprintf(msgnums, sizeof(msgnums), "%d", msgnum);
1260 make_file(fn, sizeof(fn), dir, msgnum);
1262 ast_copy_string(fn, dir, sizeof(fn));
1263 ast_filedelete(fn, NULL);
1264 if (ast_check_realtime("voicemail_data")) {
1265 ast_destroy_realtime("voicemail_data", "filename", fn, NULL);
1267 snprintf(full_fn, sizeof(full_fn), "%s.txt", fn);
1272 static int last_message_index(struct ast_vm_user *vmu, char *dir)
1279 char *argv[] = { dir };
1280 struct generic_prepare_struct gps = { .sql = sql, .argc = 1, .argv = argv };
1282 struct odbc_obj *obj;
1283 obj = ast_odbc_request_obj(odbc_database, 0);
1285 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir=?",odbc_table);
1286 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
1288 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
1289 ast_odbc_release_obj(obj);
1292 res = SQLFetch(stmt);
1293 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1294 ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
1295 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1296 ast_odbc_release_obj(obj);
1299 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
1300 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1301 ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
1302 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1303 ast_odbc_release_obj(obj);
1306 if (sscanf(rowdata, "%d", &x) != 1)
1307 ast_log(LOG_WARNING, "Failed to read message count!\n");
1308 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1309 ast_odbc_release_obj(obj);
1311 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
1316 static int message_exists(char *dir, int msgnum)
1324 char *argv[] = { dir, msgnums };
1325 struct generic_prepare_struct gps = { .sql = sql, .argc = 2, .argv = argv };
1327 struct odbc_obj *obj;
1328 obj = ast_odbc_request_obj(odbc_database, 0);
1330 snprintf(msgnums, sizeof(msgnums), "%d", msgnum);
1331 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir=? AND msgnum=?",odbc_table);
1332 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
1334 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
1335 ast_odbc_release_obj(obj);
1338 res = SQLFetch(stmt);
1339 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1340 ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
1341 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1342 ast_odbc_release_obj(obj);
1345 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
1346 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1347 ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
1348 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1349 ast_odbc_release_obj(obj);
1352 if (sscanf(rowdata, "%d", &x) != 1)
1353 ast_log(LOG_WARNING, "Failed to read message count!\n");
1354 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1355 ast_odbc_release_obj(obj);
1357 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
1362 static int count_messages(struct ast_vm_user *vmu, char *dir)
1364 return last_message_index(vmu, dir) + 1;
1367 static void delete_file(char *sdir, int smsg)
1372 char *argv[] = { sdir, msgnums };
1373 struct generic_prepare_struct gps = { .sql = sql, .argc = 2, .argv = argv };
1375 struct odbc_obj *obj;
1376 obj = ast_odbc_request_obj(odbc_database, 0);
1378 snprintf(msgnums, sizeof(msgnums), "%d", smsg);
1379 snprintf(sql, sizeof(sql), "DELETE FROM %s WHERE dir=? AND msgnum=?",odbc_table);
1380 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
1382 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
1384 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1385 ast_odbc_release_obj(obj);
1387 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
1391 static void copy_file(char *sdir, int smsg, char *ddir, int dmsg, char *dmailboxuser, char *dmailboxcontext)
1397 struct odbc_obj *obj;
1398 char *argv[] = { ddir, msgnumd, dmailboxuser, dmailboxcontext, sdir, msgnums };
1399 struct generic_prepare_struct gps = { .sql = sql, .argc = 6, .argv = argv };
1401 delete_file(ddir, dmsg);
1402 obj = ast_odbc_request_obj(odbc_database, 0);
1404 snprintf(msgnums, sizeof(msgnums), "%d", smsg);
1405 snprintf(msgnumd, sizeof(msgnumd), "%d", dmsg);
1406 snprintf(sql, sizeof(sql), "INSERT INTO %s (dir, msgnum, context, macrocontext, callerid, origtime, duration, recording, mailboxuser, mailboxcontext) SELECT ?,?,context,macrocontext,callerid,origtime,duration,recording,?,? FROM %s WHERE dir=? AND msgnum=?",odbc_table,odbc_table);
1407 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
1409 ast_log(LOG_WARNING, "SQL Execute error!\n[%s] (You probably don't have MySQL 4.1 or later installed)\n\n", sql);
1411 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
1412 ast_odbc_release_obj(obj);
1414 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
1418 static int store_file(char *dir, char *mailboxuser, char *mailboxcontext, int msgnum)
1423 void *fdm = MAP_FAILED;
1430 char full_fn[PATH_MAX];
1433 const char *context="", *macrocontext="", *callerid="", *origtime="", *duration="";
1434 const char *category = "";
1435 struct ast_config *cfg=NULL;
1436 struct odbc_obj *obj;
1437 struct ast_flags config_flags = { CONFIG_FLAG_NOCACHE };
1439 delete_file(dir, msgnum);
1440 obj = ast_odbc_request_obj(odbc_database, 0);
1442 ast_copy_string(fmt, vmfmts, sizeof(fmt));
1443 c = strchr(fmt, '|');
1446 if (!strcasecmp(fmt, "wav49"))
1448 snprintf(msgnums, sizeof(msgnums),"%d", msgnum);
1450 make_file(fn, sizeof(fn), dir, msgnum);
1452 ast_copy_string(fn, dir, sizeof(fn));
1453 snprintf(full_fn, sizeof(full_fn), "%s.txt", fn);
1454 cfg = ast_config_load(full_fn, config_flags);
1455 snprintf(full_fn, sizeof(full_fn), "%s.%s", fn, fmt);
1456 fd = open(full_fn, O_RDWR);
1458 ast_log(LOG_WARNING, "Open of sound file '%s' failed: %s\n", full_fn, strerror(errno));
1459 ast_odbc_release_obj(obj);
1463 context = ast_variable_retrieve(cfg, "message", "context");
1464 if (!context) context = "";
1465 macrocontext = ast_variable_retrieve(cfg, "message", "macrocontext");
1466 if (!macrocontext) macrocontext = "";
1467 callerid = ast_variable_retrieve(cfg, "message", "callerid");
1468 if (!callerid) callerid = "";
1469 origtime = ast_variable_retrieve(cfg, "message", "origtime");
1470 if (!origtime) origtime = "";
1471 duration = ast_variable_retrieve(cfg, "message", "duration");
1472 if (!duration) duration = "";
1473 category = ast_variable_retrieve(cfg, "message", "category");
1474 if (!category) category = "";
1476 fdlen = lseek(fd, 0, SEEK_END);
1477 lseek(fd, 0, SEEK_SET);
1478 printf("Length is %zd\n", fdlen);
1479 fdm = mmap(NULL, fdlen, PROT_READ | PROT_WRITE, MAP_SHARED,fd, 0);
1480 if (fdm == MAP_FAILED) {
1481 ast_log(LOG_WARNING, "Memory map failed!\n");
1482 ast_odbc_release_obj(obj);
1485 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
1486 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1487 ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
1488 ast_odbc_release_obj(obj);
1491 if (!ast_strlen_zero(category))
1492 snprintf(sql, sizeof(sql), "INSERT INTO %s (dir,msgnum,recording,context,macrocontext,callerid,origtime,duration,mailboxuser,mailboxcontext,category) VALUES (?,?,?,?,?,?,?,?,?,?,?)",odbc_table);
1494 snprintf(sql, sizeof(sql), "INSERT INTO %s (dir,msgnum,recording,context,macrocontext,callerid,origtime,duration,mailboxuser,mailboxcontext) VALUES (?,?,?,?,?,?,?,?,?,?)",odbc_table);
1495 res = SQLPrepare(stmt, (unsigned char *)sql, SQL_NTS);
1496 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1497 ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql);
1498 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1499 ast_odbc_release_obj(obj);
1502 len = fdlen; /* SQL_LEN_DATA_AT_EXEC(fdlen); */
1503 SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(dir), 0, (void *)dir, 0, NULL);
1504 SQLBindParameter(stmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(msgnums), 0, (void *)msgnums, 0, NULL);
1505 SQLBindParameter(stmt, 3, SQL_PARAM_INPUT, SQL_C_BINARY, SQL_LONGVARBINARY, fdlen, 0, (void *)fdm, fdlen, &len);
1506 SQLBindParameter(stmt, 4, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(context), 0, (void *)context, 0, NULL);
1507 SQLBindParameter(stmt, 5, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(macrocontext), 0, (void *)macrocontext, 0, NULL);
1508 SQLBindParameter(stmt, 6, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(callerid), 0, (void *)callerid, 0, NULL);
1509 SQLBindParameter(stmt, 7, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(origtime), 0, (void *)origtime, 0, NULL);
1510 SQLBindParameter(stmt, 8, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(duration), 0, (void *)duration, 0, NULL);
1511 SQLBindParameter(stmt, 9, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(mailboxuser), 0, (void *)mailboxuser, 0, NULL);
1512 SQLBindParameter(stmt, 10, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(mailboxcontext), 0, (void *)mailboxcontext, 0, NULL);
1513 if (!ast_strlen_zero(category))
1514 SQLBindParameter(stmt, 11, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(category), 0, (void *)category, 0, NULL);
1515 res = ast_odbc_smart_execute(obj, stmt);
1516 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1517 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
1518 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1519 ast_odbc_release_obj(obj);
1522 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1523 ast_odbc_release_obj(obj);
1525 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
1528 ast_config_destroy(cfg);
1529 if (fdm != MAP_FAILED)
1536 static void rename_file(char *sdir, int smsg, char *mailboxuser, char *mailboxcontext, char *ddir, int dmsg)
1542 struct odbc_obj *obj;
1543 char *argv[] = { ddir, msgnumd, mailboxuser, mailboxcontext, sdir, msgnums };
1544 struct generic_prepare_struct gps = { .sql = sql, .argc = 6, .argv = argv };
1546 delete_file(ddir, dmsg);
1547 obj = ast_odbc_request_obj(odbc_database, 0);
1549 snprintf(msgnums, sizeof(msgnums), "%d", smsg);
1550 snprintf(msgnumd, sizeof(msgnumd), "%d", dmsg);
1551 snprintf(sql, sizeof(sql), "UPDATE %s SET dir=?, msgnum=?, mailboxuser=?, mailboxcontext=? WHERE dir=? AND msgnum=?",odbc_table);
1552 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
1554 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
1556 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
1557 ast_odbc_release_obj(obj);
1559 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
1564 #ifndef IMAP_STORAGE
1565 static int count_messages(struct ast_vm_user *vmu, char *dir)
1567 /* Find all .txt files - even if they are not in sequence from 0000 */
1571 struct dirent *vment = NULL;
1573 if (vm_lock_path(dir))
1574 return ERROR_LOCK_PATH;
1576 if ((vmdir = opendir(dir))) {
1577 while ((vment = readdir(vmdir))) {
1578 if (strlen(vment->d_name) > 7 && !strncmp(vment->d_name + 7, ".txt", 4))
1583 ast_unlock_path(dir);
1588 static void rename_file(char *sfn, char *dfn)
1590 char stxt[PATH_MAX];
1591 char dtxt[PATH_MAX];
1592 ast_filerename(sfn,dfn,NULL);
1593 snprintf(stxt, sizeof(stxt), "%s.txt", sfn);
1594 snprintf(dtxt, sizeof(dtxt), "%s.txt", dfn);
1595 if (ast_check_realtime("voicemail_data")) {
1596 ast_update_realtime("voicemail_data", "filename", sfn, "filename", dfn, NULL);
1601 static int copy(char *infile, char *outfile)
1609 #ifdef HARDLINK_WHEN_POSSIBLE
1610 /* Hard link if possible; saves disk space & is faster */
1611 if (link(infile, outfile)) {
1613 if ((ifd = open(infile, O_RDONLY)) < 0) {
1614 ast_log(LOG_WARNING, "Unable to open %s in read-only mode\n", infile);
1617 if ((ofd = open(outfile, O_WRONLY | O_TRUNC | O_CREAT, VOICEMAIL_FILE_MODE)) < 0) {
1618 ast_log(LOG_WARNING, "Unable to open %s in write-only mode\n", outfile);
1623 len = read(ifd, buf, sizeof(buf));
1625 ast_log(LOG_WARNING, "Read failed on %s: %s\n", infile, strerror(errno));
1631 res = write(ofd, buf, len);
1632 if (errno == ENOMEM || errno == ENOSPC || res != len) {
1633 ast_log(LOG_WARNING, "Write failed on %s (%d of %d): %s\n", outfile, res, len, strerror(errno));
1643 #ifdef HARDLINK_WHEN_POSSIBLE
1645 /* Hard link succeeded */
1651 static void copy_file(char *frompath, char *topath)
1653 char frompath2[PATH_MAX], topath2[PATH_MAX];
1654 struct ast_variable *tmp,*var = NULL;
1655 char *origmailbox = NULL, *context = NULL, *macrocontext = NULL, *exten = NULL, *priority = NULL, *callerchan = NULL, *callerid = NULL, *origdate = NULL, *origtime = NULL, *category = NULL, *duration = NULL;
1656 ast_filecopy(frompath, topath, NULL);
1657 snprintf(frompath2, sizeof(frompath2), "%s.txt", frompath);
1658 snprintf(topath2, sizeof(topath2), "%s.txt", topath);
1659 if (ast_check_realtime("voicemail_data")) {
1660 var = ast_load_realtime("voicemail_data", "filename", frompath, NULL);
1661 /* This cycle converts ast_variable linked list, to va_list list of arguments, may be there is a better way to do it? */
1662 for (tmp = var; tmp; tmp = tmp->next) {
1663 if (!strcasecmp(tmp->name, "origmailbox")) {
1664 origmailbox = tmp->value;
1665 } else if (!strcasecmp(tmp->name, "context")) {
1666 context = tmp->value;
1667 } else if (!strcasecmp(tmp->name, "macrocontext")) {
1668 macrocontext = tmp->value;
1669 } else if (!strcasecmp(tmp->name, "exten")) {
1671 } else if (!strcasecmp(tmp->name, "priority")) {
1672 priority = tmp->value;
1673 } else if (!strcasecmp(tmp->name, "callerchan")) {
1674 callerchan = tmp->value;
1675 } else if (!strcasecmp(tmp->name, "callerid")) {
1676 callerid = tmp->value;
1677 } else if (!strcasecmp(tmp->name, "origdate")) {
1678 origdate = tmp->value;
1679 } else if (!strcasecmp(tmp->name, "origtime")) {
1680 origtime = tmp->value;
1681 } else if (!strcasecmp(tmp->name, "category")) {
1682 category = tmp->value;
1683 } else if (!strcasecmp(tmp->name, "duration")) {
1684 duration = tmp->value;
1687 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);
1689 copy(frompath2, topath2);
1690 ast_variables_destroy(var);
1693 * A negative return value indicates an error.
1694 * \note Should always be called with a lock already set on dir.
1696 static int last_message_index(struct ast_vm_user *vmu, char *dir)
1699 unsigned char map[MAXMSGLIMIT] = "";
1701 struct dirent *msgdirent;
1704 /* Reading the entire directory into a file map scales better than
1705 * doing a stat repeatedly on a predicted sequence. I suspect this
1706 * is partially due to stat(2) internally doing a readdir(2) itself to
1707 * find each file. */
1708 msgdir = opendir(dir);
1709 while ((msgdirent = readdir(msgdir))) {
1710 if (sscanf(msgdirent->d_name, "msg%d", &msgdirint) == 1 && msgdirint < MAXMSGLIMIT)
1715 for (x = 0; x < vmu->maxmsg; x++) {
1723 #endif /*#ifndef IMAP_STORAGE*/
1724 #endif /*#else of #ifdef ODBC_STORAGE*/
1725 #ifndef ODBC_STORAGE
1726 static int vm_delete(char *file)
1731 txtsize = (strlen(file) + 5)*sizeof(char);
1732 txt = alloca(txtsize);
1733 /* Sprintf here would safe because we alloca'd exactly the right length,
1734 * but trying to eliminate all sprintf's anyhow
1736 if (ast_check_realtime("voicemail_data")) {
1737 ast_destroy_realtime("voicemail_data", "filename", file, NULL);
1739 snprintf(txt, txtsize, "%s.txt", file);
1741 return ast_filedelete(file, NULL);
1745 static int inbuf(struct baseio *bio, FILE *fi)
1752 if ((l = fread(bio->iobuf,1,BASEMAXINLINE,fi)) <= 0) {
1766 static int inchar(struct baseio *bio, FILE *fi)
1768 if (bio->iocp>=bio->iolen) {
1769 if (!inbuf(bio, fi))
1773 return bio->iobuf[bio->iocp++];
1776 static int ochar(struct baseio *bio, int c, FILE *so)
1778 if (bio->linelength >= BASELINELEN) {
1779 if (fputs(eol,so) == EOF)
1785 if (putc(((unsigned char)c),so) == EOF)
1793 static int base_encode(char *filename, FILE *so)
1795 unsigned char dtable[BASEMAXINLINE];
1800 memset(&bio, 0, sizeof(bio));
1801 bio.iocp = BASEMAXINLINE;
1803 if (!(fi = fopen(filename, "rb"))) {
1804 ast_log(LOG_WARNING, "Failed to open file: %s: %s\n", filename, strerror(errno));
1808 for (i= 0; i<9; i++) {
1811 dtable[26+i]= 'a'+i;
1812 dtable[26+i+9]= 'j'+i;
1814 for (i= 0; i<8; i++) {
1815 dtable[i+18]= 'S'+i;
1816 dtable[26+i+18]= 's'+i;
1818 for (i= 0; i<10; i++) {
1819 dtable[52+i]= '0'+i;
1825 unsigned char igroup[3], ogroup[4];
1828 igroup[0]= igroup[1]= igroup[2]= 0;
1830 for (n= 0;n<3;n++) {
1831 if ((c = inchar(&bio, fi)) == EOF) {
1836 igroup[n]= (unsigned char)c;
1840 ogroup[0]= dtable[igroup[0]>>2];
1841 ogroup[1]= dtable[((igroup[0]&3)<<4) | (igroup[1]>>4)];
1842 ogroup[2]= dtable[((igroup[1]&0xF)<<2) | (igroup[2]>>6)];
1843 ogroup[3]= dtable[igroup[2]&0x3F];
1853 ochar(&bio, ogroup[i], so);
1857 if (fputs(eol,so) == EOF)
1865 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)
1868 /* Prepare variables for substitution in email body and subject */
1869 pbx_builtin_setvar_helper(ast, "VM_NAME", vmu->fullname);
1870 pbx_builtin_setvar_helper(ast, "VM_DUR", dur);
1871 snprintf(passdata, passdatasize, "%d", msgnum);
1872 pbx_builtin_setvar_helper(ast, "VM_MSGNUM", passdata);
1873 pbx_builtin_setvar_helper(ast, "VM_CONTEXT", context);
1874 pbx_builtin_setvar_helper(ast, "VM_MAILBOX", mailbox);
1875 pbx_builtin_setvar_helper(ast, "VM_CALLERID", ast_callerid_merge(callerid, sizeof(callerid), cidname, cidnum, "Unknown Caller"));
1876 pbx_builtin_setvar_helper(ast, "VM_CIDNAME", (cidname ? cidname : "an unknown caller"));
1877 pbx_builtin_setvar_helper(ast, "VM_CIDNUM", (cidnum ? cidnum : "an unknown caller"));
1878 pbx_builtin_setvar_helper(ast, "VM_DATE", date);
1879 pbx_builtin_setvar_helper(ast, "VM_CATEGORY", category ? ast_strdupa(category) : "no category");
1882 static char *quote(const char *from, char *to, size_t len)
1886 for (; ptr < to + len - 1; from++) {
1889 else if (*from == '\0')
1893 if (ptr < to + len - 1)
1900 * fill in *tm for current time according to the proper timezone, if any.
1901 * Return tm so it can be used as a function argument.
1903 static const struct ast_tm *vmu_tm(const struct ast_vm_user *vmu, struct ast_tm *tm)
1905 const struct vm_zone *z = NULL;
1906 struct timeval t = ast_tvnow();
1908 /* Does this user have a timezone specified? */
1909 if (!ast_strlen_zero(vmu->zonetag)) {
1910 /* Find the zone in the list */
1911 AST_LIST_LOCK(&zones);
1912 AST_LIST_TRAVERSE(&zones, z, list) {
1913 if (!strcmp(z->name, vmu->zonetag))
1916 AST_LIST_UNLOCK(&zones);
1918 ast_localtime(&t, tm, z ? z->timezone : NULL);
1922 /*! \brief same as mkstemp, but return a FILE * */
1923 static FILE *vm_mkftemp(char *template)
1926 int pfd = mkstemp(template);
1927 chmod(template, VOICEMAIL_FILE_MODE & ~my_umask);
1929 p = fdopen(pfd, "w+");
1938 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 *format, int duration, int attach_user_voicemail, struct ast_channel *chan, const char *category, int imap)
1941 char host[MAXHOSTNAMELEN] = "";
1949 size_t len_passdata;
1950 char *greeting_attachment;
1958 gethostname(host, sizeof(host)-1);
1959 if (strchr(srcemail, '@'))
1960 ast_copy_string(who, srcemail, sizeof(who));
1962 snprintf(who, sizeof(who), "%s@%s", srcemail, host);
1964 greeting_attachment = strrchr(ast_strdupa(attach), '/');
1965 if (greeting_attachment)
1966 *greeting_attachment++ = '\0';
1968 snprintf(dur, sizeof(dur), "%d:%02d", duration / 60, duration % 60);
1969 ast_strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", vmu_tm(vmu, &tm));
1970 fprintf(p, "Date: %s" ENDL, date);
1972 /* Set date format for voicemail mail */
1973 ast_strftime(date, sizeof(date), emaildateformat, &tm);
1975 if (!ast_strlen_zero(fromstring)) {
1976 struct ast_channel *ast;
1977 if ((ast = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, 0))) {
1979 int vmlen = strlen(fromstring)*3 + 200;
1980 if ((passdata = alloca(vmlen))) {
1981 memset(passdata, 0, vmlen);
1982 prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category);
1983 pbx_substitute_variables_helper(ast, fromstring, passdata, vmlen);
1984 len_passdata = strlen(passdata) * 2 + 3;
1985 passdata2 = alloca(len_passdata);
1986 fprintf(p, "From: %s <%s>" ENDL, quote(passdata, passdata2, len_passdata), who);
1988 ast_log(LOG_WARNING, "Cannot allocate workspace for variable substitution\n");
1989 ast_channel_free(ast);
1991 ast_log(LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
1993 fprintf(p, "From: Asterisk PBX <%s>" ENDL, who);
1994 len_passdata = strlen(vmu->fullname) * 2 + 3;
1995 passdata2 = alloca(len_passdata);
1996 fprintf(p, "To: %s <%s>" ENDL, quote(vmu->fullname, passdata2, len_passdata), vmu->email);
1997 if (!ast_strlen_zero(emailsubject)) {
1998 struct ast_channel *ast;
1999 if ((ast = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, 0))) {
2001 int vmlen = strlen(emailsubject) * 3 + 200;
2002 if ((passdata = alloca(vmlen))) {
2003 memset(passdata, 0, vmlen);
2004 prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category);
2005 pbx_substitute_variables_helper(ast, emailsubject, passdata, vmlen);
2006 fprintf(p, "Subject: %s" ENDL, passdata);
2008 ast_log(LOG_WARNING, "Cannot allocate workspace for variable substitution\n");
2009 ast_channel_free(ast);
2011 ast_log(LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
2012 } else if (!ast_strlen_zero(emailtitle)) {
2013 fprintf(p, emailtitle, msgnum + 1, mailbox) ;
2015 } else if (ast_test_flag((&globalflags), VM_PBXSKIP))
2016 fprintf(p, "Subject: New message %d in mailbox %s" ENDL, msgnum + 1, mailbox);
2018 fprintf(p, "Subject: [PBX]: New message %d in mailbox %s" ENDL, msgnum + 1, mailbox);
2019 fprintf(p, "Message-ID: <Asterisk-%d-%d-%s-%d@%s>" ENDL, msgnum + 1, (unsigned int)ast_random(), mailbox, (int)getpid(), host);
2021 /* additional information needed for IMAP searching */
2022 fprintf(p, "X-Asterisk-VM-Message-Num: %d" ENDL, msgnum + 1);
2023 /* fprintf(p, "X-Asterisk-VM-Orig-Mailbox: %s" ENDL, ext); */
2024 fprintf(p, "X-Asterisk-VM-Server-Name: %s" ENDL, fromstring);
2025 fprintf(p, "X-Asterisk-VM-Context: %s" ENDL, context);
2026 fprintf(p, "X-Asterisk-VM-Extension: %s" ENDL, mailbox);
2027 fprintf(p, "X-Asterisk-VM-Priority: %d" ENDL, chan->priority);
2028 fprintf(p, "X-Asterisk-VM-Caller-channel: %s" ENDL, chan->name);
2029 fprintf(p, "X-Asterisk-VM-Caller-ID-Num: %s" ENDL, cidnum);
2030 fprintf(p, "X-Asterisk-VM-Caller-ID-Name: %s" ENDL, cidname);
2031 fprintf(p, "X-Asterisk-VM-Duration: %d" ENDL, duration);
2032 if (!ast_strlen_zero(category))
2033 fprintf(p, "X-Asterisk-VM-Category: %s" ENDL, category);
2034 fprintf(p, "X-Asterisk-VM-Message-Type: %s\n", msgnum > -1 ? "Message" : greeting_attachment);
2035 fprintf(p, "X-Asterisk-VM-Orig-date: %s" ENDL, date);
2036 fprintf(p, "X-Asterisk-VM-Orig-time: %ld" ENDL, (long)time(NULL));
2038 if (!ast_strlen_zero(cidnum))
2039 fprintf(p, "X-Asterisk-CallerID: %s" ENDL, cidnum);
2040 if (!ast_strlen_zero(cidname))
2041 fprintf(p, "X-Asterisk-CallerIDName: %s" ENDL, cidname);
2042 fprintf(p, "MIME-Version: 1.0" ENDL);
2043 if (attach_user_voicemail) {
2044 /* Something unique. */
2045 snprintf(bound, sizeof(bound), "----voicemail_%d%s%d%d", msgnum + 1, mailbox, (int)getpid(), (unsigned int)ast_random());
2047 fprintf(p, "Content-Type: multipart/mixed; boundary=\"%s\"" ENDL, bound);
2048 fprintf(p, ENDL ENDL "This is a multi-part message in MIME format." ENDL ENDL);
2049 fprintf(p, "--%s" ENDL, bound);
2051 fprintf(p, "Content-Type: text/plain; charset=%s" ENDL "Content-Transfer-Encoding: 8bit" ENDL ENDL, charset);
2053 struct ast_channel *ast;
2054 if ((ast = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, 0))) {
2056 int vmlen = strlen(emailbody)*3 + 200;
2057 if ((passdata = alloca(vmlen))) {
2058 memset(passdata, 0, vmlen);
2059 prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category);
2060 pbx_substitute_variables_helper(ast, emailbody, passdata, vmlen);
2061 fprintf(p, "%s" ENDL, passdata);
2063 ast_log(LOG_WARNING, "Cannot allocate workspace for variable substitution\n");
2064 ast_channel_free(ast);
2066 ast_log(LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
2067 } else if (msgnum > -1){
2068 fprintf(p, "Dear %s:" ENDL ENDL "\tJust wanted to let you know you were just left a %s long message (number %d)" ENDL
2070 "in mailbox %s from %s, on %s so you might" ENDL
2071 "want to check it when you get a chance. Thanks!" ENDL ENDL "\t\t\t\t--Asterisk" ENDL ENDL, vmu->fullname,
2072 dur, msgnum + 1, mailbox, (cidname ? cidname : (cidnum ? cidnum : "an unknown caller")), date);
2074 fprintf(p, "This message is to let you know that your greeting was changed on %s." ENDL
2075 "Please do not delete this message, lest your greeting vanish with it." ENDL ENDL, date);
2077 if (attach_user_voicemail) {
2078 /* Eww. We want formats to tell us their own MIME type */
2079 char *ctype = (!strcasecmp(format, "ogg")) ? "application/" : "audio/x-";
2080 char tmpdir[256], newtmp[256];
2083 if (vmu->volgain < -.001 || vmu->volgain > .001) {
2084 create_dirpath(tmpdir, sizeof(tmpdir), vmu->context, vmu->mailbox, "tmp");
2085 snprintf(newtmp, sizeof(newtmp), "%s/XXXXXX", tmpdir);
2086 tmpfd = mkstemp(newtmp);
2087 chmod(newtmp, VOICEMAIL_FILE_MODE & ~my_umask);
2088 ast_debug(3, "newtmp: %s\n", newtmp);
2090 snprintf(tmpcmd, sizeof(tmpcmd), "sox -v %.4f %s.%s %s.%s", vmu->volgain, attach, format, newtmp, format);
2091 ast_safe_system(tmpcmd);
2093 ast_debug(3, "VOLGAIN: Stored at: %s.%s - Level: %.4f - Mailbox: %s\n", attach, format, vmu->volgain, mailbox);
2096 fprintf(p, "--%s" ENDL, bound);
2098 fprintf(p, "Content-Type: %s%s; name=\"msg%04d.%s\"" ENDL, ctype, format, msgnum + 1, format);
2100 fprintf(p, "Content-Type: %s%s; name=\"%s.%s\"" ENDL, ctype, format, greeting_attachment, format);
2101 fprintf(p, "Content-Transfer-Encoding: base64" ENDL);
2102 fprintf(p, "Content-Description: Voicemail sound attachment." ENDL);
2104 fprintf(p, "Content-Disposition: attachment; filename=\"msg%04d.%s\"" ENDL ENDL, msgnum + 1, format);
2106 fprintf(p, "Content-Disposition: attachment; filename=\"%s.%s\"" ENDL ENDL, greeting_attachment, format);
2107 snprintf(fname, sizeof(fname), "%s.%s", attach, format);
2108 base_encode(fname, p);
2109 fprintf(p, ENDL "--%s--" ENDL "." ENDL, bound);
2119 static int sendmail(char *srcemail, struct ast_vm_user *vmu, int msgnum, char *context, char *mailbox, char *cidnum, char *cidname, char *attach, char *format, int duration, int attach_user_voicemail, struct ast_channel *chan, const char *category)
2122 char tmp[80] = "/tmp/astmail-XXXXXX";
2125 if (vmu && ast_strlen_zero(vmu->email)) {
2126 ast_log(LOG_WARNING, "E-mail address missing for mailbox [%s]. E-mail will not be sent.\n", vmu->mailbox);
2129 if (!strcmp(format, "wav49"))
2131 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));
2132 /* Make a temporary file instead of piping directly to sendmail, in case the mail
2134 if ((p = vm_mkftemp(tmp)) == NULL) {
2135 ast_log(LOG_WARNING, "Unable to launch '%s' (can't create temporary file)\n", mailcmd);
2138 make_email_file(p, srcemail, vmu, msgnum, context, mailbox, cidnum, cidname, attach, format, duration, attach_user_voicemail, chan, category, 0);
2140 snprintf(tmp2, sizeof(tmp2), "( %s < %s ; rm -f %s ) &", mailcmd, tmp, tmp);
2141 ast_safe_system(tmp2);
2142 ast_debug(1, "Sent mail to %s with command '%s'\n", vmu->email, mailcmd);
2147 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)
2150 char host[MAXHOSTNAMELEN] = "";
2153 char tmp[80] = "/tmp/astmail-XXXXXX";
2154 char tmp2[PATH_MAX];
2158 if ((p = vm_mkftemp(tmp)) == NULL) {
2159 ast_log(LOG_WARNING, "Unable to launch '%s' (can't create temporary file)\n", mailcmd);
2162 gethostname(host, sizeof(host)-1);
2163 if (strchr(srcemail, '@'))
2164 ast_copy_string(who, srcemail, sizeof(who));
2166 snprintf(who, sizeof(who), "%s@%s", srcemail, host);
2167 snprintf(dur, sizeof(dur), "%d:%02d", duration / 60, duration % 60);
2168 ast_strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", vmu_tm(vmu, &tm));
2169 fprintf(p, "Date: %s\n", date);
2171 if (*pagerfromstring) {
2172 struct ast_channel *ast;
2173 if ((ast = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, 0))) {
2175 int vmlen = strlen(fromstring)*3 + 200;
2176 if ((passdata = alloca(vmlen))) {
2177 memset(passdata, 0, vmlen);
2178 prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category);
2179 pbx_substitute_variables_helper(ast, pagerfromstring, passdata, vmlen);
2180 fprintf(p, "From: %s <%s>\n", passdata, who);
2182 ast_log(LOG_WARNING, "Cannot allocate workspace for variable substitution\n");
2183 ast_channel_free(ast);
2185 ast_log(LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
2187 fprintf(p, "From: Asterisk PBX <%s>\n", who);
2188 fprintf(p, "To: %s\n", pager);
2190 struct ast_channel *ast;
2191 if ((ast = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, 0))) {
2193 int vmlen = strlen(pagersubject) * 3 + 200;
2194 if ((passdata = alloca(vmlen))) {
2195 memset(passdata, 0, vmlen);
2196 prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category);
2197 pbx_substitute_variables_helper(ast, pagersubject, passdata, vmlen);
2198 fprintf(p, "Subject: %s\n\n", passdata);
2200 ast_log(LOG_WARNING, "Cannot allocate workspace for variable substitution\n");
2201 ast_channel_free(ast);
2203 ast_log(LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
2205 fprintf(p, "Subject: New VM\n\n");
2207 ast_strftime(date, sizeof(date), "%A, %B %d, %Y at %r", &tm);
2209 struct ast_channel *ast;
2210 if ((ast = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, 0))) {
2212 int vmlen = strlen(pagerbody) * 3 + 200;
2213 passdata = alloca(vmlen);
2214 memset(passdata, 0, vmlen);
2215 prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category);
2216 pbx_substitute_variables_helper(ast, pagerbody, passdata, vmlen);
2217 fprintf(p, "%s\n", passdata);
2218 ast_channel_free(ast);
2220 ast_log(LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
2222 fprintf(p, "New %s long msg in box %s\n"
2223 "from %s, on %s", dur, mailbox, (cidname ? cidname : (cidnum ? cidnum : "unknown")), date);
2226 snprintf(tmp2, sizeof(tmp2), "( %s < %s ; rm -f %s ) &", mailcmd, tmp, tmp);
2227 ast_safe_system(tmp2);
2228 ast_debug(1, "Sent page to %s with command '%s'\n", pager, mailcmd);
2232 static int get_date(char *s, int len)
2235 struct timeval t = ast_tvnow();
2236 ast_localtime(&t, &tm, NULL);
2237 return ast_strftime(s, len, "%a %b %e %r %Z %Y", &tm);
2240 static int invent_message(struct ast_channel *chan, char *context, char *ext, int busy, char *ecodes)
2244 char dest[PATH_MAX];
2246 snprintf(fn, sizeof(fn), "%s%s/%s/greet", VM_SPOOL_DIR, context, ext);
2248 if ((res = create_dirpath(dest, sizeof(dest), context, ext, ""))) {
2249 ast_log(LOG_WARNING, "Failed to make directory(%s)\n", fn);
2253 RETRIEVE(fn, -1, ext, context);
2254 if (ast_fileexists(fn, NULL, NULL) > 0) {
2255 res = ast_stream_and_wait(chan, fn, ecodes);
2261 /* Dispose just in case */
2263 res = ast_stream_and_wait(chan, "vm-theperson", ecodes);
2266 res = ast_say_digit_str(chan, ext, ecodes, chan->language);
2270 res = ast_stream_and_wait(chan, busy ? "vm-isonphone" : "vm-isunavail", ecodes);
2274 static void free_user(struct ast_vm_user *vmu)
2276 if (!ast_test_flag(vmu, VM_ALLOCED))
2282 static void free_zone(struct vm_zone *z)
2287 static const char *mbox(int id)
2289 static const char *msgs[] = {
2301 return (id >= 0 && id < (sizeof(msgs)/sizeof(msgs[0]))) ? msgs[id] : "Unknown";
2304 static int folder_int(const char *folder)
2306 /*assume a NULL folder means INBOX*/
2309 if(!strcasecmp(folder, "INBOX"))
2311 else if (!strcasecmp(folder, "Old"))
2313 else if (!strcasecmp(folder, "Work"))
2315 else if (!strcasecmp(folder, "Family"))
2317 else if (!strcasecmp(folder, "Friends"))
2319 else if (!strcasecmp(folder, "Cust1"))
2321 else if (!strcasecmp(folder, "Cust2"))
2323 else if (!strcasecmp(folder, "Cust3"))
2325 else if (!strcasecmp(folder, "Cust4"))
2327 else if (!strcasecmp(folder, "Cust5"))
2329 else /*assume they meant INBOX if folder is not found otherwise*/
2335 /*! XXX \todo Fix this function to support multiple mailboxes in the intput string */
2336 static int inboxcount(const char *mailbox, int *newmsgs, int *oldmsgs)
2343 char tmp[PATH_MAX] = "";
2344 struct odbc_obj *obj;
2346 struct generic_prepare_struct gps = { .sql = sql, .argc = 0 };
2353 /* If no mailbox, return immediately */
2354 if (ast_strlen_zero(mailbox))
2357 ast_copy_string(tmp, mailbox, sizeof(tmp));
2359 context = strchr(tmp, '@');
2364 context = "default";
2366 obj = ast_odbc_request_obj(odbc_database, 0);
2368 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir = '%s%s/%s/%s'", odbc_table, VM_SPOOL_DIR, context, tmp, "INBOX");
2369 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
2371 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
2372 ast_odbc_release_obj(obj);
2375 res = SQLFetch(stmt);
2376 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2377 ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
2378 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2379 ast_odbc_release_obj(obj);
2382 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
2383 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2384 ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
2385 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2386 ast_odbc_release_obj(obj);
2389 *newmsgs = atoi(rowdata);
2390 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2392 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir = '%s%s/%s/%s'", odbc_table, VM_SPOOL_DIR, context, tmp, "Old");
2393 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
2395 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
2396 ast_odbc_release_obj(obj);
2399 res = SQLFetch(stmt);
2400 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2401 ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
2402 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2403 ast_odbc_release_obj(obj);
2406 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
2407 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2408 ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
2409 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2410 ast_odbc_release_obj(obj);
2413 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2414 ast_odbc_release_obj(obj);
2415 *oldmsgs = atoi(rowdata);
2418 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
2424 static int messagecount(const char *context, const char *mailbox, const char *folder)
2426 struct odbc_obj *obj = NULL;
2429 SQLHSTMT stmt = NULL;
2432 struct generic_prepare_struct gps = { .sql = sql, .argc = 0 };
2435 /* If no mailbox, return immediately */
2436 if (ast_strlen_zero(mailbox))
2439 obj = ast_odbc_request_obj(odbc_database, 0);
2441 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir = '%s%s/%s/%s'", odbc_table, VM_SPOOL_DIR, context, mailbox, folder);
2442 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
2444 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
2447 res = SQLFetch(stmt);
2448 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2449 ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
2450 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2453 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
2454 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2455 ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
2456 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2459 nummsgs = atoi(rowdata);
2460 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2462 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
2466 ast_odbc_release_obj(obj);
2470 static int has_voicemail(const char *mailbox, const char *folder)
2472 char tmp[256], *tmp2 = tmp, *mbox, *context;
2473 ast_copy_string(tmp, mailbox, sizeof(tmp));
2474 while ((context = mbox = strsep(&tmp2, ","))) {
2475 strsep(&context, "@");
2476 if (ast_strlen_zero(context))
2477 context = "default";
2478 if (messagecount(context, mbox, folder))
2484 #elif defined(IMAP_STORAGE)
2486 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)
2488 char *myserveremail = serveremail;
2493 char tmp[80] = "/tmp/astmail-XXXXXX";
2498 /* Attach only the first format */
2499 fmt = ast_strdupa(fmt);
2501 strsep(&stringp, "|");
2503 if (!ast_strlen_zero(vmu->serveremail))
2504 myserveremail = vmu->serveremail;
2507 make_file(fn, sizeof(fn), dir, msgnum);
2509 ast_copy_string (fn, dir, sizeof(fn));
2511 if (ast_strlen_zero(vmu->email))
2512 ast_copy_string(vmu->email, vmu->imapuser, sizeof(vmu->email));
2514 if (!strcmp(fmt, "wav49"))
2516 ast_debug(3, "Storing file '%s', format '%s'\n", fn, fmt);
2518 /* Make a temporary file instead of piping directly to sendmail, in case the mail
2520 if (!(p = vm_mkftemp(tmp))) {
2521 ast_log(LOG_WARNING, "Unable to store '%s' (can't create temporary file)\n", fn);
2526 if (msgnum < 0 && imapgreetings) {
2527 init_mailstream(vms, GREETINGS_FOLDER);
2528 imap_delete_old_greeting(fn, vms);
2531 make_email_file(p, myserveremail, vmu, msgnum, vmu->context, vmu->mailbox, S_OR(chan->cid.cid_num, NULL), S_OR(chan->cid.cid_name, NULL), fn, fmt, duration, 1, chan, NULL, 1);
2532 /* read mail file to memory */
2535 if (!(buf = ast_malloc(len+1))) {
2536 ast_log(LOG_ERROR, "Can't allocate %ld bytes to read message\n", len+1);
2539 fread(buf, len, 1, p);
2540 ((char *)buf)[len] = '\0';
2541 INIT(&str, mail_string, buf, len);
2542 init_mailstream(vms, NEW_FOLDER);
2543 imap_mailbox_name(mailbox, vms, NEW_FOLDER, 1);
2544 if(!mail_append(vms->mailstream, mailbox, &str))
2545 ast_log(LOG_ERROR, "Error while sending the message to %s\n", mailbox);
2549 ast_debug(3, "%s stored\n", fn);
2554 static int messagecount(const char *context, const char *mailbox, const char *folder)
2559 struct ast_vm_user *vmu, vmus;
2560 struct vm_state *vms_p;
2562 int fold = folder_int(folder);
2564 if (ast_strlen_zero(mailbox))
2567 /* We have to get the user before we can open the stream! */
2568 /* ast_log (LOG_DEBUG,"Before find_user, context is %s and mailbox is %s\n",context,mailbox); */
2569 vmu = find_user(&vmus, context, mailbox);
2571 ast_log (LOG_ERROR,"Couldn't find mailbox %s in context %s\n",mailbox,context);
2574 /* No IMAP account available */
2575 if (vmu->imapuser[0] == '\0') {
2576 ast_log (LOG_WARNING,"IMAP user not set for mailbox %s\n",vmu->mailbox);
2581 /* No IMAP account available */
2582 if (vmu->imapuser[0] == '\0') {
2583 ast_log (LOG_WARNING,"IMAP user not set for mailbox %s\n",vmu->mailbox);
2588 /* check if someone is accessing this box right now... */
2589 vms_p = get_vm_state_by_imapuser(vmu->imapuser,1);
2591 vms_p = get_vm_state_by_mailbox(mailbox,1);
2594 ast_debug(3, "Returning before search - user is logged in\n");
2595 if(fold == 0) {/*INBOX*/
2596 return vms_p->newmessages;
2598 if(fold == 1) {/*Old messages*/
2599 return vms_p->oldmessages;
2603 /* add one if not there... */
2604 vms_p = get_vm_state_by_imapuser(vmu->imapuser,0);
2606 vms_p = get_vm_state_by_mailbox(mailbox,0);
2610 ast_debug(3,"Adding new vmstate for %s\n",vmu->imapuser);
2611 if (!(vms_p = ast_calloc(1, sizeof(*vms_p)))) {
2614 ast_copy_string(vms_p->imapuser,vmu->imapuser, sizeof(vms_p->imapuser));
2615 ast_copy_string(vms_p->username, mailbox, sizeof(vms_p->username)); /* save for access from interactive entry point */
2616 vms_p->mailstream = NIL; /* save for access from interactive entry point */
2617 ast_debug(3, "Copied %s to %s\n",vmu->imapuser,vms_p->imapuser);
2619 /* set mailbox to INBOX! */
2620 ast_copy_string(vms_p->curbox, mbox(fold), sizeof(vms_p->curbox));
2621 init_vm_state(vms_p);
2622 vmstate_insert(vms_p);
2624 ret = init_mailstream(vms_p, fold);
2625 if (!vms_p->mailstream) {
2626 ast_log (LOG_ERROR,"Houston we have a problem - IMAP mailstream is NULL\n");
2630 pgm = mail_newsearchpgm ();
2631 hdr = mail_newsearchheader ("X-Asterisk-VM-Extension", (char *)mailbox);
2637 /* In the special case where fold is 1 (old messages) we have to do things a bit
2638 * differently. Old messages are stored in the INBOX but are marked as "seen"
2647 vms_p->vmArrayIndex = 0;
2648 mail_search_full (vms_p->mailstream, NULL, pgm, NIL);
2650 vms_p->newmessages = vms_p->vmArrayIndex;
2652 vms_p->oldmessages = vms_p->vmArrayIndex;
2653 /*Freeing the searchpgm also frees the searchhdr*/
2654 mail_free_searchpgm(&pgm);
2656 return vms_p->vmArrayIndex;
2658 mail_ping(vms_p->mailstream);
2662 static int inboxcount(const char *mailbox_context, int *newmsgs, int *oldmsgs)
2664 char tmp[PATH_MAX] = "";
2674 ast_debug(3,"Mailbox is set to %s\n",mailbox_context);
2675 /* If no mailbox, return immediately */
2676 if (ast_strlen_zero(mailbox_context))
2679 ast_copy_string(tmp, mailbox_context, sizeof(tmp));
2680 context = strchr(tmp, '@');
2681 if (strchr(mailbox_context, ',')) {
2683 ast_copy_string(tmp, mailbox_context, sizeof(tmp));
2685 while((cur = strsep(&mb, ", "))) {
2686 if (!ast_strlen_zero(cur)) {
2687 if (inboxcount(cur, newmsgs ? &tmpnew : NULL, oldmsgs ? &tmpold : NULL))
2704 context = "default";
2705 mailboxnc = (char *)mailbox_context;
2708 if((*newmsgs = messagecount(context, mailboxnc, "INBOX")) < 0)
2712 if((*oldmsgs = messagecount(context, mailboxnc, "Old")) < 0)
2719 static int has_voicemail(const char *mailbox, const char *folder)
2721 char tmp[256], *tmp2, *mbox, *context;
2722 ast_copy_string(tmp, mailbox, sizeof(tmp));
2724 if(strchr(tmp2, ',')) {
2725 while((mbox = strsep(&tmp2, ","))) {
2726 if(!ast_strlen_zero(mbox)) {
2727 if (has_voicemail(mbox, folder))
2732 if ((context= strchr(tmp, '@')))
2735 context = "default";
2736 return messagecount(context, tmp, folder) ? 1 : 0;
2739 static int copy_message(struct ast_channel *chan, struct ast_vm_user *vmu, int imbox, int msgnum, long duration, struct ast_vm_user *recip, char *fmt, char *dir)
2742 struct vm_state *sendvms = NULL, *destvms = NULL;
2743 char messagestring[10]; /*I guess this could be a problem if someone has more than 999999999 messages...*/
2744 if(!(sendvms = get_vm_state_by_imapuser(vmu->imapuser, 2)))
2746 ast_log(LOG_ERROR, "Couldn't get vm_state for originator's mailbox!!\n");
2749 if(!(destvms = get_vm_state_by_imapuser(recip->imapuser, 2)))
2751 ast_log(LOG_ERROR, "Couldn't get vm_state for destination mailbox!\n");
2754 imap_mailbox_name(dest, destvms, imbox, 1);
2755 snprintf(messagestring, sizeof(messagestring), "%ld", sendvms->msgArray[msgnum]);
2756 if((mail_copy(sendvms->mailstream, messagestring, dest) == T))
2758 ast_log(LOG_WARNING, "Unable to copy message from mailbox %s to mailbox %s\n", vmu->mailbox, recip->mailbox);
2763 #ifndef IMAP_STORAGE
2764 /* copy message only used by file storage */
2765 static int copy_message(struct ast_channel *chan, struct ast_vm_user *vmu, int imbox, int msgnum, long duration, struct ast_vm_user *recip, char *fmt, char *dir)
2767 char fromdir[PATH_MAX], todir[PATH_MAX], frompath[PATH_MAX], topath[PATH_MAX];
2768 const char *frombox = mbox(imbox);
2771 ast_log(LOG_NOTICE, "Copying message from %s@%s to %s@%s\n", vmu->mailbox, vmu->context, recip->mailbox, recip->context);
2773 create_dirpath(todir, sizeof(todir), recip->context, recip->mailbox, "INBOX");
2776 make_dir(fromdir, sizeof(fromdir), vmu->context, vmu->mailbox, frombox);
2778 ast_copy_string(fromdir, dir, sizeof(fromdir));
2780 make_file(frompath, sizeof(frompath), fromdir, msgnum);
2781 make_dir(todir, sizeof(todir), recip->context, recip->mailbox, frombox);
2783 if (vm_lock_path(todir))
2784 return ERROR_LOCK_PATH;
2786 recipmsgnum = last_message_index(recip, todir) + 1;
2787 if (recipmsgnum < recip->maxmsg) {
2788 make_file(topath, sizeof(topath), todir, recipmsgnum);
2789 COPY(fromdir, msgnum, todir, recipmsgnum, recip->mailbox, recip->context, frompath, topath);
2791 ast_log(LOG_ERROR, "Recipient mailbox %s@%s is full\n", recip->mailbox, recip->context);
2793 ast_unlock_path(todir);
2794 notify_new_message(chan, recip, recipmsgnum, duration, fmt, S_OR(chan->cid.cid_num, NULL), S_OR(chan->cid.cid_name, NULL));
2799 #if !(defined(IMAP_STORAGE) || defined(ODBC_STORAGE))
2800 static int messagecount(const char *context, const char *mailbox, const char *folder)
2802 return __has_voicemail(context, mailbox, folder, 0);
2806 static int __has_voicemail(const char *context, const char *mailbox, const char *folder, int shortcircuit)
2813 /* If no mailbox, return immediately */
2814 if (ast_strlen_zero(mailbox))
2817 if (ast_strlen_zero(folder))
2819 if (ast_strlen_zero(context))
2820 context = "default";
2822 snprintf(fn, sizeof(fn), "%s%s/%s/%s", VM_SPOOL_DIR, context, mailbox, folder);
2824 if (!(dir = opendir(fn)))
2827 while ((de = readdir(dir))) {
2828 if (!strncasecmp(de->d_name, "msg", 3)) {
2832 } else if (!strncasecmp(de->d_name + 8, "txt", 3))
2843 static int has_voicemail(const char *mailbox, const char *folder)
2845 char tmp[256], *tmp2 = tmp, *mbox, *context;
2846 ast_copy_string(tmp, mailbox, sizeof(tmp));
2847 while ((mbox = strsep(&tmp2, ","))) {
2848 if ((context = strchr(mbox, '@')))
2851 context = "default";
2852 if (__has_voicemail(context, mbox, folder, 1))
2859 static int inboxcount(const char *mailbox, int *newmsgs, int *oldmsgs)
2864 /* If no mailbox, return immediately */
2865 if (ast_strlen_zero(mailbox))
2873 if (strchr(mailbox, ',')) {
2877 ast_copy_string(tmp, mailbox, sizeof(tmp));
2879 while ((cur = strsep(&mb, ", "))) {
2880 if (!ast_strlen_zero(cur)) {
2881 if (inboxcount(cur, newmsgs ? &tmpnew : NULL, oldmsgs ? &tmpold : NULL))
2894 ast_copy_string(tmp, mailbox, sizeof(tmp));
2896 if ((context = strchr(tmp, '@')))
2899 context = "default";
2902 *newmsgs = __has_voicemail(context, tmp, "INBOX", 0);
2904 *oldmsgs = __has_voicemail(context, tmp, "Old", 0);
2911 static void run_externnotify(char *context, char *extension)
2913 char arguments[255];
2914 char ext_context[256] = "";
2915 int newvoicemails = 0, oldvoicemails = 0;
2916 struct ast_smdi_mwi_message *mwi_msg;
2918 if (!ast_strlen_zero(context))
2919 snprintf(ext_context, sizeof(ext_context), "%s@%s", extension, context);
2921 ast_copy_string(ext_context, extension, sizeof(ext_context));
2924 if (ast_app_has_voicemail(ext_context, NULL))
2925 ast_smdi_mwi_set(smdi_iface, extension);
2927 ast_smdi_mwi_unset(smdi_iface, extension);
2929 if ((mwi_msg = ast_smdi_mwi_message_wait(smdi_iface, SMDI_MWI_WAIT_TIMEOUT))) {
2930 ast_log(LOG_ERROR, "Error executing SMDI MWI change for %s on %s\n", extension, smdi_iface->name);
2931 if (!strncmp(mwi_msg->cause, "INV", 3))
2932 ast_log(LOG_ERROR, "Invalid MWI extension: %s\n", mwi_msg->fwd_st);
2933 else if (!strncmp(mwi_msg->cause, "BLK", 3))
2934 ast_log(LOG_WARNING, "MWI light was already on or off for %s\n", mwi_msg->fwd_st);
2935 ast_log(LOG_WARNING, "The switch reported '%s'\n", mwi_msg->cause);
2936 ASTOBJ_UNREF(mwi_msg, ast_smdi_mwi_message_destroy);
2938 ast_debug(1, "Successfully executed SMDI MWI change for %s on %s\n", extension, smdi_iface->name);
2942 if (!ast_strlen_zero(externnotify)) {
2943 if (inboxcount(ext_context, &newvoicemails, &oldvoicemails)) {
2944 ast_log(LOG_ERROR, "Problem in calculating number of voicemail messages available for extension %s\n", extension);
2946 snprintf(arguments, sizeof(arguments), "%s %s %s %d&", externnotify, context, extension, newvoicemails);
2947 ast_debug(1, "Executing %s\n", arguments);
2948 ast_safe_system(arguments);
2953 struct leave_vm_options {
2955 signed char record_gain;
2958 static int leave_voicemail(struct ast_channel *chan, char *ext, struct leave_vm_options *options)
2961 int newmsgs, oldmsgs;
2962 struct vm_state *vms = NULL;
2964 char txtfile[PATH_MAX], tmptxtfile[PATH_MAX];
2980 char dir[PATH_MAX], tmpdir[PATH_MAX];
2982 char prefile[PATH_MAX] = "";
2983 char tempfile[PATH_MAX] = "";
2984 char ext_context[256] = "";
2987 char ecodes[16] = "#";
2988 char tmp[1024] = "", *tmpptr;
2989 struct ast_vm_user *vmu;
2990 struct ast_vm_user svm;
2991 const char *category = NULL;
2993 ast_copy_string(tmp, ext, sizeof(tmp));
2995 if ((context = strchr(tmp, '@'))) {
2997 tmpptr = strchr(context, '&');
2999 tmpptr = strchr(ext, '&');
3005 category = pbx_builtin_getvar_helper(chan, "VM_CATEGORY");
3007 ast_debug(3, "Before find_user\n");
3008 if (!(vmu = find_user(&svm, context, ext))) {
3009 ast_log(LOG_WARNING, "No entry in voicemail config file for '%s'\n", ext);
3010 pbx_builtin_setvar_helper(chan, "VMSTATUS", "FAILED");
3013 /* Setup pre-file if appropriate */
3014 if (strcmp(vmu->context, "default"))
3015 snprintf(ext_context, sizeof(ext_context), "%s@%s", ext, vmu->context);
3017 ast_copy_string(ext_context, vmu->mailbox, sizeof(ext_context));
3018 if (ast_test_flag(options, OPT_BUSY_GREETING)) {
3019 snprintf(prefile, sizeof(prefile), "%s%s/%s/busy", VM_SPOOL_DIR, vmu->context, ext);
3020 } else if (ast_test_flag(options, OPT_UNAVAIL_GREETING)) {
3021 snprintf(prefile, sizeof(prefile), "%s%s/%s/unavail", VM_SPOOL_DIR, vmu->context, ext);
3023 snprintf(tempfile, sizeof(tempfile), "%s%s/%s/temp", VM_SPOOL_DIR, vmu->context, ext);
3024 if ((res = create_dirpath(tmpdir, sizeof(tmpdir), vmu->context, ext, "tmp"))) {
3025 ast_log(LOG_WARNING, "Failed to make directory (%s)\n", tempfile);
3028 RETRIEVE(tempfile, -1, ext, context);
3029 if (ast_fileexists(tempfile, NULL, NULL) > 0)
3030 ast_copy_string(prefile, tempfile, sizeof(prefile));
3031 DISPOSE(tempfile, -1);
3032 /* It's easier just to try to make it than to check for its existence */
3033 create_dirpath(dir, sizeof(dir), vmu->context, ext, "INBOX");
3035 /* Check current or macro-calling context for special extensions */
3036 if (ast_test_flag(vmu, VM_OPERATOR)) {
3037 if (!ast_strlen_zero(vmu->exit)) {
3038 if (ast_exists_extension(chan, vmu->exit, "o", 1, chan->cid.cid_num)) {
3039 strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
3042 } else if (ast_exists_extension(chan, chan->context, "o", 1, chan->cid.cid_num)) {
3043 strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
3046 else if (!ast_strlen_zero(chan->macrocontext) && ast_exists_extension(chan, chan->macrocontext, "o", 1, chan->cid.cid_num)) {
3047 strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
3052 if (!ast_strlen_zero(vmu->exit)) {
3053 if (ast_exists_extension(chan, vmu->exit, "a", 1, chan->cid.cid_num))
3054 strncat(ecodes, "*", sizeof(ecodes) - strlen(ecodes) - 1);
3055 } else if (ast_exists_extension(chan, chan->context, "a", 1, chan->cid.cid_num))
3056 strncat(ecodes, "*", sizeof(ecodes) - strlen(ecodes) - 1);
3057 else if (!ast_strlen_zero(chan->macrocontext) && ast_exists_extension(chan, chan->macrocontext, "a", 1, chan->cid.cid_num)) {
3058 strncat(ecodes, "*", sizeof(ecodes) - strlen(ecodes) - 1);
3062 /* Play the beginning intro if desired */
3063 if (!ast_strlen_zero(prefile)) {
3067 RETRIEVE(prefile, -1, ext, context);
3068 if (ast_fileexists(prefile, NULL, NULL) > 0) {
3069 if (ast_streamfile(chan, prefile, chan->language) > -1)
3070 res = ast_waitstream(chan, ecodes);
3072 if (success == -1) {
3073 /* We couldn't retrieve the file from the database, but we found it on the file system. Let's put it in the database. */
3074 ast_debug(1, "Greeting not retrieved from database, but found in file storage. Inserting into database\n");
3075 store_file(prefile, vmu->mailbox, vmu->context, -1);
3079 ast_debug(1, "%s doesn't exist, doing what we can\n", prefile);
3080 res = invent_message(chan, vmu->context, ext, ast_test_flag(options, OPT_BUSY_GREETING), ecodes);
3082 DISPOSE(prefile, -1);
3084 ast_debug(1, "Hang up during prefile playback\n");
3086 pbx_builtin_setvar_helper(chan, "VMSTATUS", "FAILED");
3091 /* On a '#' we skip the instructions */
3092 ast_set_flag(options, OPT_SILENT);
3095 if (!res && !ast_test_flag(options, OPT_SILENT)) {
3096 res = ast_stream_and_wait(chan, INTRO, ecodes);