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">
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(void);
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)
281 German requires the following additional soundfile:
282 \arg \b 1F einE (feminine)
284 Spanish requires the following additional soundfile:
285 \arg \b 1M un (masculine)
287 Dutch, Portuguese & Spanish require the following additional soundfiles:
288 \arg \b vm-INBOXs singular of 'new'
289 \arg \b vm-Olds singular of 'old/heard/read'
292 \arg \b vm-INBOX nieuwe (nl)
293 \arg \b vm-Old oude (nl)
296 \arg \b vm-new-a 'new', feminine singular accusative
297 \arg \b vm-new-e 'new', feminine plural accusative
298 \arg \b vm-new-ych 'new', feminine plural genitive
299 \arg \b vm-old-a 'old', feminine singular accusative
300 \arg \b vm-old-e 'old', feminine plural accusative
301 \arg \b vm-old-ych 'old', feminine plural genitive
302 \arg \b digits/1-a 'one', not always same as 'digits/1'
303 \arg \b digits/2-ie 'two', not always same as 'digits/2'
306 \arg \b vm-nytt singular of 'new'
307 \arg \b vm-nya plural of 'new'
308 \arg \b vm-gammalt singular of 'old'
309 \arg \b vm-gamla plural of 'old'
310 \arg \b digits/ett 'one', not always same as 'digits/1'
313 \arg \b vm-ny singular of 'new'
314 \arg \b vm-nye plural of 'new'
315 \arg \b vm-gammel singular of 'old'
316 \arg \b vm-gamle plural of 'old'
324 Italian requires the following additional soundfile:
328 \arg \b vm-nuovi new plural
329 \arg \b vm-vecchio old
330 \arg \b vm-vecchi old plural
332 Chinese (Taiwan) requires the following additional soundfile:
333 \arg \b vm-tong A class-word for call (tong1)
334 \arg \b vm-ri A class-word for day (ri4)
335 \arg \b vm-you You (ni3)
336 \arg \b vm-haveno Have no (mei2 you3)
337 \arg \b vm-have Have (you3)
338 \arg \b vm-listen To listen (yao4 ting1)
341 \note Don't use vm-INBOX or vm-Old, because they are the name of the INBOX and Old folders,
342 spelled among others when you have to change folder. For the above reasons, vm-INBOX
343 and vm-Old are spelled plural, to make them sound more as folder name than an adjective.
352 unsigned char iobuf[BASEMAXINLINE];
355 /*! Structure for linked list of users
356 * Use ast_vm_user_destroy() to free one of these structures. */
358 char context[AST_MAX_CONTEXT]; /*!< Voicemail context */
359 char mailbox[AST_MAX_EXTENSION]; /*!< Mailbox id, unique within vm context */
360 char password[80]; /*!< Secret pin code, numbers only */
361 char fullname[80]; /*!< Full name, for directory app */
362 char email[80]; /*!< E-mail address */
363 char pager[80]; /*!< E-mail address to pager (no attachment) */
364 char serveremail[80]; /*!< From: Mail address */
365 char mailcmd[160]; /*!< Configurable mail command */
366 char language[MAX_LANGUAGE]; /*!< Config: Language setting */
367 char zonetag[80]; /*!< Time zone */
370 char uniqueid[20]; /*!< Unique integer identifier */
372 char attachfmt[20]; /*!< Attachment format */
373 unsigned int flags; /*!< VM_ flags */
375 int maxmsg; /*!< Maximum number of msgs per folder for this mailbox */
376 int maxsecs; /*!< Maximum number of seconds per message for this mailbox */
378 char imapuser[80]; /*!< IMAP server login */
379 char imappassword[80]; /*!< IMAP server password if authpassword not defined */
381 double volgain; /*!< Volume gain for voicemails sent via email */
382 AST_LIST_ENTRY(ast_vm_user) list;
385 /*! Voicemail time zones */
387 AST_LIST_ENTRY(vm_zone) list;
390 char msg_format[512];
393 /*! Voicemail mailbox state */
397 char curdir[PATH_MAX];
398 char vmbox[PATH_MAX];
410 int updated; /*!< decremented on each mail check until 1 -allows delay */
412 MAILSTREAM *mailstream;
414 char imapuser[80]; /*!< IMAP server login */
416 unsigned int quota_limit;
417 unsigned int quota_usage;
418 struct vm_state *persist_vms;
424 static char odbc_database[80];
425 static char odbc_table[80];
426 #define RETRIEVE(a,b,c,d) retrieve_file(a,b)
427 #define DISPOSE(a,b) remove_file(a,b)
428 #define STORE(a,b,c,d,e,f,g,h,i) store_file(a,b,c,d)
429 #define EXISTS(a,b,c,d) (message_exists(a,b))
430 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(a,b,c,d,e,f))
431 #define COPY(a,b,c,d,e,f,g,h) (copy_file(a,b,c,d,e,f))
432 #define DELETE(a,b,c) (delete_file(a,b))
435 #define RETRIEVE(a,b,c,d) (imap_retrieve_file(a,b,c,d ))
436 #define DISPOSE(a,b) (imap_remove_file(a,b))
437 #define STORE(a,b,c,d,e,f,g,h,i) (imap_store_file(a,b,c,d,e,f,g,h,i))
438 #define EXISTS(a,b,c,d) (ast_fileexists(c,NULL,d) > 0)
439 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(g,h));
440 #define COPY(a,b,c,d,e,f,g,h) (copy_file(g,h));
441 #define IMAP_DELETE(a,b,c,d) (vm_imap_delete(b,d))
442 #define DELETE(a,b,c) (vm_delete(c))
444 #define RETRIEVE(a,b,c,d)
446 #define STORE(a,b,c,d,e,f,g,h,i)
447 #define EXISTS(a,b,c,d) (ast_fileexists(c,NULL,d) > 0)
448 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(g,h));
449 #define COPY(a,b,c,d,e,f,g,h) (copy_file(g,h));
450 #define DELETE(a,b,c) (vm_delete(c))
454 static char VM_SPOOL_DIR[PATH_MAX];
456 static char ext_pass_cmd[128];
458 #define PWDCHANGE_INTERNAL (1 << 1)
459 #define PWDCHANGE_EXTERNAL (1 << 2)
460 static int pwdchange = PWDCHANGE_INTERNAL;
463 #define tdesc "Comedian Mail (Voicemail System) with ODBC Storage"
466 # define tdesc "Comedian Mail (Voicemail System) with IMAP Storage"
468 # define tdesc "Comedian Mail (Voicemail System)"
472 static char userscontext[AST_MAX_EXTENSION] = "default";
474 static char *addesc = "Comedian Mail";
476 static char *synopsis_vm = "Leave a Voicemail message";
478 static char *descrip_vm =
479 " VoiceMail(mailbox[@context][&mailbox[@context]][...][|options]): This\n"
480 "application allows the calling party to leave a message for the specified\n"
481 "list of mailboxes. When multiple mailboxes are specified, the greeting will\n"
482 "be taken from the first mailbox specified. Dialplan execution will stop if the\n"
483 "specified mailbox does not exist.\n"
484 " The Voicemail application will exit if any of the following DTMF digits are\n"
486 " 0 - Jump to the 'o' extension in the current dialplan context.\n"
487 " * - Jump to the 'a' extension in the current dialplan context.\n"
488 " This application will set the following channel variable upon completion:\n"
489 " VMSTATUS - This indicates the status of the execution of the VoiceMail\n"
490 " application. The possible values are:\n"
491 " SUCCESS | USEREXIT | FAILED\n\n"
493 " b - Play the 'busy' greeting to the calling party.\n"
494 " g(#) - Use the specified amount of gain when recording the voicemail\n"
495 " message. The units are whole-number decibels (dB).\n"
496 " s - Skip the playback of instructions for leaving a message to the\n"
498 " u - Play the 'unavailable' greeting.\n";
500 static char *synopsis_vmain = "Check Voicemail messages";
502 static char *descrip_vmain =
503 " VoiceMailMain([mailbox][@context][|options]): This application allows the\n"
504 "calling party to check voicemail messages. A specific mailbox, and optional\n"
505 "corresponding context, may be specified. If a mailbox is not provided, the\n"
506 "calling party will be prompted to enter one. If a context is not specified,\n"
507 "the 'default' context will be used.\n\n"
509 " p - Consider the mailbox parameter as a prefix to the mailbox that\n"
510 " is entered by the caller.\n"
511 " g(#) - Use the specified amount of gain when recording a voicemail\n"
512 " message. The units are whole-number decibels (dB).\n"
513 " s - Skip checking the passcode for the mailbox.\n"
514 " a(#) - Skip folder prompt and go directly to folder specified.\n"
515 " Defaults to INBOX\n";
517 static char *synopsis_vm_box_exists =
518 "Check to see if Voicemail mailbox exists";
520 static char *descrip_vm_box_exists =
521 " MailboxExists(mailbox[@context][|options]): Check to see if the specified\n"
522 "mailbox exists. If no voicemail context is specified, the 'default' context\n"
524 " This application will set the following channel variable upon completion:\n"
525 " VMBOXEXISTSSTATUS - This will contain the status of the execution of the\n"
526 " MailboxExists application. Possible values include:\n"
527 " SUCCESS | FAILED\n\n"
528 " Options: (none)\n";
530 static char *synopsis_vmauthenticate = "Authenticate with Voicemail passwords";
532 static char *descrip_vmauthenticate =
533 " VMAuthenticate([mailbox][@context][|options]): This application behaves the\n"
534 "same way as the Authenticate application, but the passwords are taken from\n"
536 " If the mailbox is specified, only that mailbox's password will be considered\n"
537 "valid. If the mailbox is not specified, the channel variable AUTH_MAILBOX will\n"
538 "be set with the authenticated mailbox.\n\n"
540 " s - Skip playing the initial prompts.\n";
542 /* Leave a message */
543 static char *app = "VoiceMail";
545 /* Check mail, control, etc */
546 static char *app2 = "VoiceMailMain";
548 static char *app3 = "MailboxExists";
549 static char *app4 = "VMAuthenticate";
551 static AST_LIST_HEAD_STATIC(users, ast_vm_user);
552 static AST_LIST_HEAD_STATIC(zones, vm_zone);
553 static int maxsilence;
555 static int silencethreshold = 128;
556 static char serveremail[80];
557 static char mailcmd[160]; /* Configurable mail cmd */
558 static char externnotify[160];
559 static struct ast_smdi_interface *smdi_iface = NULL;
560 static char vmfmts[80];
561 static double volgain;
562 static int vmminsecs;
563 static int vmmaxsecs;
566 static int maxlogins;
568 /*! Poll mailboxes for changes since there is something external to
569 * app_voicemail that may change them. */
570 static unsigned int poll_mailboxes;
572 /*! Polling frequency */
573 static unsigned int poll_freq;
574 /*! By default, poll every 30 seconds */
575 #define DEFAULT_POLL_FREQ 30
577 AST_MUTEX_DEFINE_STATIC(poll_lock);
578 static ast_cond_t poll_cond = PTHREAD_COND_INITIALIZER;
579 static pthread_t poll_thread = AST_PTHREADT_NULL;
580 static unsigned char poll_thread_run;
582 /*! Subscription to ... MWI event subscriptions */
583 static struct ast_event_sub *mwi_sub_sub;
584 /*! Subscription to ... MWI event un-subscriptions */
585 static struct ast_event_sub *mwi_unsub_sub;
588 * \brief An MWI subscription
590 * This is so we can keep track of which mailboxes are subscribed to.
591 * This way, we know which mailboxes to poll when the pollmailboxes
592 * option is being used.
595 AST_RWLIST_ENTRY(mwi_sub) entry;
602 static AST_RWLIST_HEAD_STATIC(mwi_subs, mwi_sub);
604 /* custom audio control prompts for voicemail playback */
605 static char listen_control_forward_key[12];
606 static char listen_control_reverse_key[12];
607 static char listen_control_pause_key[12];
608 static char listen_control_restart_key[12];
609 static char listen_control_stop_key[12];
611 /* custom password sounds */
612 static char vm_password[80] = "vm-password";
613 static char vm_newpassword[80] = "vm-newpassword";
614 static char vm_passchanged[80] = "vm-passchanged";
615 static char vm_reenterpassword[80] = "vm-reenterpassword";
616 static char vm_mismatch[80] = "vm-mismatch";
618 static struct ast_flags globalflags = {0};
620 static int saydurationminfo;
622 static char dialcontext[AST_MAX_CONTEXT];
623 static char callcontext[AST_MAX_CONTEXT];
624 static char exitcontext[AST_MAX_CONTEXT];
626 static char cidinternalcontexts[MAX_NUM_CID_CONTEXTS][64];
629 static char *emailbody = NULL;
630 static char *emailsubject = NULL;
631 static char *pagerbody = NULL;
632 static char *pagersubject = NULL;
633 static char fromstring[100];
634 static char pagerfromstring[100];
635 static char emailtitle[100];
636 static char charset[32] = "ISO-8859-1";
638 static unsigned char adsifdn[4] = "\x00\x00\x00\x0F";
639 static unsigned char adsisec[4] = "\x9B\xDB\xF7\xAC";
640 static int adsiver = 1;
641 static char emaildateformat[32] = "%A, %B %d, %Y at %r";
643 /* Forward declarations - generic */
644 static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu,int box);
645 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);
646 static int dialout(struct ast_channel *chan, struct ast_vm_user *vmu, char *num, char *outgoing_context);
647 static int play_record_review(struct ast_channel *chan, char *playfile, char *recordfile, int maxtime,
648 char *fmt, int outsidecaller, struct ast_vm_user *vmu, int *duration, const char *unlockdir,
649 signed char record_gain, struct vm_state *vms);
650 static int vm_tempgreeting(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, char *fmtc, signed char record_gain);
651 static int vm_play_folder_name(struct ast_channel *chan, char *mbox);
652 static int notify_new_message(struct ast_channel *chan, struct ast_vm_user *vmu, int msgnum, long duration, char *fmt, char *cidnum, char *cidname);
653 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);
654 static void apply_options(struct ast_vm_user *vmu, const char *options);
655 static int is_valid_dtmf(const char *key);
657 #if !(defined(ODBC_STORAGE) || defined(IMAP_STORAGE))
658 static int __has_voicemail(const char *context, const char *mailbox, const char *folder, int shortcircuit);
663 static void populate_defaults(struct ast_vm_user *vmu)
665 ast_copy_flags(vmu, (&globalflags), AST_FLAGS_ALL);
666 if (saydurationminfo)
667 vmu->saydurationm = saydurationminfo;
669 ast_copy_string(vmu->callback, callcontext, sizeof(vmu->callback));
671 ast_copy_string(vmu->dialout, dialcontext, sizeof(vmu->dialout));
673 ast_copy_string(vmu->exit, exitcontext, sizeof(vmu->exit));
675 vmu->maxsecs = vmmaxsecs;
677 vmu->maxmsg = maxmsg;
678 vmu->volgain = volgain;
681 static void apply_option(struct ast_vm_user *vmu, const char *var, const char *value)
684 if (!strcasecmp(var, "attach")) {
685 ast_set2_flag(vmu, ast_true(value), VM_ATTACH);
686 } else if (!strcasecmp(var, "attachfmt")) {
687 ast_copy_string(vmu->attachfmt, value, sizeof(vmu->attachfmt));
688 } else if (!strcasecmp(var, "serveremail")) {
689 ast_copy_string(vmu->serveremail, value, sizeof(vmu->serveremail));
690 } else if (!strcasecmp(var, "language")) {
691 ast_copy_string(vmu->language, value, sizeof(vmu->language));
692 } else if (!strcasecmp(var, "tz")) {
693 ast_copy_string(vmu->zonetag, value, sizeof(vmu->zonetag));
695 } else if (!strcasecmp(var, "imapuser")) {
696 ast_copy_string(vmu->imapuser, value, sizeof(vmu->imapuser));
697 } else if (!strcasecmp(var, "imappassword")) {
698 ast_copy_string(vmu->imappassword, value, sizeof(vmu->imappassword));
700 } else if (!strcasecmp(var, "delete") || !strcasecmp(var, "deletevoicemail")) {
701 ast_set2_flag(vmu, ast_true(value), VM_DELETE);
702 } else if (!strcasecmp(var, "saycid")){
703 ast_set2_flag(vmu, ast_true(value), VM_SAYCID);
704 } else if (!strcasecmp(var,"sendvoicemail")){
705 ast_set2_flag(vmu, ast_true(value), VM_SVMAIL);
706 } else if (!strcasecmp(var, "review")){
707 ast_set2_flag(vmu, ast_true(value), VM_REVIEW);
708 } else if (!strcasecmp(var, "tempgreetwarn")){
709 ast_set2_flag(vmu, ast_true(value), VM_TEMPGREETWARN);
710 } else if (!strcasecmp(var, "operator")){
711 ast_set2_flag(vmu, ast_true(value), VM_OPERATOR);
712 } else if (!strcasecmp(var, "envelope")){
713 ast_set2_flag(vmu, ast_true(value), VM_ENVELOPE);
714 } else if (!strcasecmp(var, "sayduration")){
715 ast_set2_flag(vmu, ast_true(value), VM_SAYDURATION);
716 } else if (!strcasecmp(var, "saydurationm")){
717 if (sscanf(value, "%d", &x) == 1) {
718 vmu->saydurationm = x;
720 ast_log(LOG_WARNING, "Invalid min duration for say duration\n");
722 } else if (!strcasecmp(var, "forcename")){
723 ast_set2_flag(vmu, ast_true(value), VM_FORCENAME);
724 } else if (!strcasecmp(var, "forcegreetings")){
725 ast_set2_flag(vmu, ast_true(value), VM_FORCEGREET);
726 } else if (!strcasecmp(var, "callback")) {
727 ast_copy_string(vmu->callback, value, sizeof(vmu->callback));
728 } else if (!strcasecmp(var, "dialout")) {
729 ast_copy_string(vmu->dialout, value, sizeof(vmu->dialout));
730 } else if (!strcasecmp(var, "exitcontext")) {
731 ast_copy_string(vmu->exit, value, sizeof(vmu->exit));
732 } else if (!strcasecmp(var, "maxmessage")) {
733 if (vmu->maxsecs <= 0) {
734 ast_log(LOG_WARNING, "Invalid max message length of %s. Using global value %i\n", value, vmmaxsecs);
735 vmu->maxsecs = vmmaxsecs;
737 vmu->maxsecs = atoi(value);
739 } else if (!strcasecmp(var, "maxmsg")) {
740 vmu->maxmsg = atoi(value);
741 if (vmu->maxmsg <= 0) {
742 ast_log(LOG_WARNING, "Invalid number of messages per folder maxmsg=%s. Using default value %i\n", value, MAXMSG);
743 vmu->maxmsg = MAXMSG;
744 } else if (vmu->maxmsg > MAXMSGLIMIT) {
745 ast_log(LOG_WARNING, "Maximum number of messages per folder is %i. Cannot accept value maxmsg=%s\n", MAXMSGLIMIT, value);
746 vmu->maxmsg = MAXMSGLIMIT;
748 } else if (!strcasecmp(var, "volgain")) {
749 sscanf(value, "%lf", &vmu->volgain);
750 } else if (!strcasecmp(var, "options")) {
751 apply_options(vmu, value);
755 static int change_password_realtime(struct ast_vm_user *vmu, const char *password)
758 if (!ast_strlen_zero(vmu->uniqueid)) {
759 res = ast_update_realtime("voicemail", "uniqueid", vmu->uniqueid, "password", password, NULL);
761 ast_copy_string(vmu->password, password, sizeof(vmu->password));
771 static void apply_options(struct ast_vm_user *vmu, const char *options)
772 { /* Destructively Parse options and apply */
776 stringp = ast_strdupa(options);
777 while ((s = strsep(&stringp, "|"))) {
779 if ((var = strsep(&value, "=")) && value) {
780 apply_option(vmu, var, value);
785 static void apply_options_full(struct ast_vm_user *retval, struct ast_variable *var)
787 struct ast_variable *tmp;
790 if (!strcasecmp(tmp->name, "vmsecret")) {
791 ast_copy_string(retval->password, tmp->value, sizeof(retval->password));
792 } else if (!strcasecmp(tmp->name, "secret") || !strcasecmp(tmp->name, "password")) { /* don't overwrite vmsecret if it exists */
793 if (ast_strlen_zero(retval->password))
794 ast_copy_string(retval->password, tmp->value, sizeof(retval->password));
795 } else if (!strcasecmp(tmp->name, "uniqueid")) {
796 ast_copy_string(retval->uniqueid, tmp->value, sizeof(retval->uniqueid));
797 } else if (!strcasecmp(tmp->name, "pager")) {
798 ast_copy_string(retval->pager, tmp->value, sizeof(retval->pager));
799 } else if (!strcasecmp(tmp->name, "email")) {
800 ast_copy_string(retval->email, tmp->value, sizeof(retval->email));
801 } else if (!strcasecmp(tmp->name, "fullname")) {
802 ast_copy_string(retval->fullname, tmp->value, sizeof(retval->fullname));
803 } else if (!strcasecmp(tmp->name, "context")) {
804 ast_copy_string(retval->context, tmp->value, sizeof(retval->context));
806 } else if (!strcasecmp(tmp->name, "imapuser")) {
807 ast_copy_string(retval->imapuser, tmp->value, sizeof(retval->imapuser));
808 } else if (!strcasecmp(tmp->name, "imappassword")) {
809 ast_copy_string(retval->imappassword, tmp->value, sizeof(retval->imappassword));
812 apply_option(retval, tmp->name, tmp->value);
817 static int is_valid_dtmf(const char *key)
820 char *local_key = ast_strdupa(key);
822 for(i = 0; i < strlen(key); ++i) {
823 if(!strchr(VALID_DTMF, *local_key)) {
824 ast_log(LOG_WARNING, "Invalid DTMF key \"%c\" used in voicemail configuration file\n", *local_key);
832 static struct ast_vm_user *find_user_realtime(struct ast_vm_user *ivm, const char *context, const char *mailbox)
834 struct ast_variable *var;
835 struct ast_vm_user *retval;
837 if ((retval = (ivm ? ivm : ast_calloc(1, sizeof(*retval))))) {
839 ast_set_flag(retval, VM_ALLOCED);
841 memset(retval, 0, sizeof(*retval));
843 ast_copy_string(retval->mailbox, mailbox, sizeof(retval->mailbox));
844 populate_defaults(retval);
845 if (!context && ast_test_flag((&globalflags), VM_SEARCH))
846 var = ast_load_realtime("voicemail", "mailbox", mailbox, NULL);
848 var = ast_load_realtime("voicemail", "mailbox", mailbox, "context", context, NULL);
850 apply_options_full(retval, var);
851 ast_variables_destroy(var);
861 static struct ast_vm_user *find_user(struct ast_vm_user *ivm, const char *context, const char *mailbox)
863 /* This function could be made to generate one from a database, too */
864 struct ast_vm_user *vmu=NULL, *cur;
865 AST_LIST_LOCK(&users);
867 if (!context && !ast_test_flag((&globalflags), VM_SEARCH))
870 AST_LIST_TRAVERSE(&users, cur, list) {
871 if (ast_test_flag((&globalflags), VM_SEARCH) && !strcasecmp(mailbox, cur->mailbox))
873 if (context && (!strcasecmp(context, cur->context)) && (!strcasecmp(mailbox, cur->mailbox)))
877 /* Make a copy, so that on a reload, we have no race */
878 if ((vmu = (ivm ? ivm : ast_malloc(sizeof(*vmu))))) {
879 memcpy(vmu, cur, sizeof(*vmu));
880 ast_set2_flag(vmu, !ivm, VM_ALLOCED);
881 AST_LIST_NEXT(vmu, list) = NULL;
884 vmu = find_user_realtime(ivm, context, mailbox);
885 AST_LIST_UNLOCK(&users);
889 static int reset_user_pw(const char *context, const char *mailbox, const char *newpass)
891 /* This function could be made to generate one from a database, too */
892 struct ast_vm_user *cur;
894 AST_LIST_LOCK(&users);
895 AST_LIST_TRAVERSE(&users, cur, list) {
896 if ((!context || !strcasecmp(context, cur->context)) &&
897 (!strcasecmp(mailbox, cur->mailbox)))
901 ast_copy_string(cur->password, newpass, sizeof(cur->password));
904 AST_LIST_UNLOCK(&users);
908 static void vm_change_password(struct ast_vm_user *vmu, const char *newpassword)
910 struct ast_config *cfg=NULL;
911 struct ast_variable *var=NULL;
912 struct ast_category *cat=NULL;
913 char *category=NULL, *value=NULL, *new=NULL;
914 const char *tmp=NULL;
916 if (!change_password_realtime(vmu, newpassword))
919 /* check voicemail.conf */
920 if ((cfg = ast_config_load_with_comments(VOICEMAIL_CONFIG))) {
921 while ((category = ast_category_browse(cfg, category))) {
922 if (!strcasecmp(category, vmu->context)) {
923 if (!(tmp = ast_variable_retrieve(cfg, category, vmu->mailbox))) {
924 ast_log(LOG_WARNING, "We could not find the mailbox.\n");
927 value = strstr(tmp,",");
929 ast_log(LOG_WARNING, "variable has bad format.\n");
932 new = alloca((strlen(value)+strlen(newpassword)+1));
933 sprintf(new,"%s%s", newpassword, value);
934 if (!(cat = ast_category_get(cfg, category))) {
935 ast_log(LOG_WARNING, "Failed to get category structure.\n");
938 ast_variable_update(cat, vmu->mailbox, new, NULL, 0);
941 /* save the results */
942 reset_user_pw(vmu->context, vmu->mailbox, newpassword);
943 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
944 config_text_file_save(VOICEMAIL_CONFIG, cfg, "AppVoicemail");
948 /* check users.conf and update the password stored for the mailbox*/
949 /* if no vmsecret entry exists create one. */
950 if ((cfg = ast_config_load_with_comments("users.conf"))) {
951 ast_debug(4, "we are looking for %s\n", vmu->mailbox);
952 while ((category = ast_category_browse(cfg, category))) {
953 ast_debug(4, "users.conf: %s\n", category);
954 if (!strcasecmp(category, vmu->mailbox)) {
955 if (!(tmp = ast_variable_retrieve(cfg, category, "vmsecret"))) {
956 ast_debug(3, "looks like we need to make vmsecret!\n");
957 var = ast_variable_new("vmsecret", newpassword);
959 new = alloca(strlen(newpassword)+1);
960 sprintf(new, "%s", newpassword);
961 if (!(cat = ast_category_get(cfg, category))) {
962 ast_debug(4, "failed to get category!\n");
966 ast_variable_update(cat, "vmsecret", new, NULL, 0);
968 ast_variable_append(cat, var);
971 /* save the results and clean things up */
972 reset_user_pw(vmu->context, vmu->mailbox, newpassword);
973 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
974 config_text_file_save("users.conf", cfg, "AppVoicemail");
978 static void vm_change_password_shell(struct ast_vm_user *vmu, char *newpassword)
981 snprintf(buf,255,"%s %s %s %s",ext_pass_cmd,vmu->context,vmu->mailbox,newpassword);
982 if (!ast_safe_system(buf))
983 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
986 static int make_dir(char *dest, int len, const char *context, const char *ext, const char *folder)
988 return snprintf(dest, len, "%s%s/%s/%s", VM_SPOOL_DIR, context, ext, folder);
992 static int make_gsm_file(char *dest, char *imapuser, char *dir, int num)
995 if ((res = ast_mkdir(dir, 01777))) {
996 ast_log(LOG_WARNING, "ast_mkdir '%s' failed: %s\n", dir, strerror(res));
997 return sprintf(dest, "%s/msg%04d", dir, num);
999 /* return sprintf(dest, "%s/s/msg%04d", dir, imapuser, num); */
1000 return sprintf(dest, "%s/msg%04d", dir, num);
1003 static void vm_imap_delete(int msgnum, struct vm_state *vms)
1005 unsigned long messageNum = 0;
1008 /* find real message number based on msgnum */
1009 /* this may be an index into vms->msgArray based on the msgnum. */
1011 messageNum = vms->msgArray[msgnum];
1012 if (messageNum == 0) {
1013 ast_log(LOG_WARNING, "msgnum %d, mailbox message %lu is zero.\n",msgnum,messageNum);
1016 ast_debug(3, "deleting msgnum %d, which is mailbox message %lu\n",msgnum,messageNum);
1017 /* delete message */
1018 sprintf (arg,"%lu",messageNum);
1019 mail_setflag (vms->mailstream,arg,"\\DELETED");
1023 static int make_file(char *dest, int len, char *dir, int num)
1025 return snprintf(dest, len, "%s/msg%04d", dir, num);
1028 /*! \brief basically mkdir -p $dest/$context/$ext/$folder
1029 * \param dest String. base directory.
1030 * \param len Length of dest.
1031 * \param context String. Ignored if is null or empty string.
1032 * \param ext String. Ignored if is null or empty string.
1033 * \param folder String. Ignored if is null or empty string.
1034 * \return -1 on failure, 0 on success.
1036 static int create_dirpath(char *dest, int len, const char *context, const char *ext, const char *folder)
1038 mode_t mode = VOICEMAIL_DIR_MODE;
1041 make_dir(dest, len, context, ext, folder);
1042 if ((res = ast_mkdir(dest, mode))) {
1043 ast_log(LOG_WARNING, "ast_mkdir '%s' failed: %s\n", dest, strerror(res));
1049 /*! \brief Lock file path
1050 only return failure if ast_lock_path returns 'timeout',
1051 not if the path does not exist or any other reason
1053 static int vm_lock_path(const char *path)
1055 switch (ast_lock_path(path)) {
1056 case AST_LOCK_TIMEOUT:
1065 static int retrieve_file(char *dir, int msgnum)
1071 void *fdm = MAP_FAILED;
1072 SQLSMALLINT colcount=0;
1079 SQLSMALLINT datatype;
1080 SQLSMALLINT decimaldigits;
1081 SQLSMALLINT nullable;
1087 char full_fn[PATH_MAX];
1090 struct odbc_obj *obj;
1091 obj = ast_odbc_request_obj(odbc_database, 0);
1093 ast_copy_string(fmt, vmfmts, sizeof(fmt));
1094 c = strchr(fmt, '|');
1097 if (!strcasecmp(fmt, "wav49"))
1099 snprintf(msgnums, sizeof(msgnums),"%d", msgnum);
1101 make_file(fn, sizeof(fn), dir, msgnum);
1103 ast_copy_string(fn, dir, sizeof(fn));
1104 snprintf(full_fn, sizeof(full_fn), "%s.txt", fn);
1106 if (!(f = fopen(full_fn, "w+"))) {
1107 ast_log(LOG_WARNING, "Failed to open/create '%s'\n", full_fn);
1111 snprintf(full_fn, sizeof(full_fn), "%s.%s", fn, fmt);
1112 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
1113 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1114 ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
1115 ast_odbc_release_obj(obj);
1118 snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE dir=? AND msgnum=?",odbc_table);
1119 res = SQLPrepare(stmt, (unsigned char *)sql, SQL_NTS);
1120 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1121 ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql);
1122 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1123 ast_odbc_release_obj(obj);
1126 SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(dir), 0, (void *)dir, 0, NULL);
1127 SQLBindParameter(stmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(msgnums), 0, (void *)msgnums, 0, NULL);
1128 res = ast_odbc_smart_execute(obj, stmt);
1129 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1130 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
1131 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1132 ast_odbc_release_obj(obj);
1135 res = SQLFetch(stmt);
1136 if (res == SQL_NO_DATA) {
1137 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1138 ast_odbc_release_obj(obj);
1141 else if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1142 ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
1143 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1144 ast_odbc_release_obj(obj);
1147 fd = open(full_fn, O_RDWR | O_CREAT | O_TRUNC, VOICEMAIL_FILE_MODE);
1149 ast_log(LOG_WARNING, "Failed to write '%s': %s\n", full_fn, strerror(errno));
1150 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1151 ast_odbc_release_obj(obj);
1154 res = SQLNumResultCols(stmt, &colcount);
1155 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1156 ast_log(LOG_WARNING, "SQL Column Count error!\n[%s]\n\n", sql);
1157 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1158 ast_odbc_release_obj(obj);
1162 fprintf(f, "[message]\n");
1163 for (x=0;x<colcount;x++) {
1165 collen = sizeof(coltitle);
1166 res = SQLDescribeCol(stmt, x + 1, (unsigned char *)coltitle, sizeof(coltitle), &collen,
1167 &datatype, &colsize, &decimaldigits, &nullable);
1168 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1169 ast_log(LOG_WARNING, "SQL Describe Column error!\n[%s]\n\n", sql);
1170 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1171 ast_odbc_release_obj(obj);
1174 if (!strcasecmp(coltitle, "recording")) {
1176 res = SQLGetData(stmt, x + 1, SQL_BINARY, rowdata, 0, &colsize2);
1180 lseek(fd, fdlen - 1, SEEK_SET);
1181 if (write(fd, tmp, 1) != 1) {
1186 /* Read out in small chunks */
1187 for (offset = 0; offset < colsize2; offset += CHUNKSIZE) {
1188 if ((fdm = mmap(NULL, CHUNKSIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, offset)) == MAP_FAILED) {
1189 ast_log(LOG_WARNING, "Could not mmap the output file: %s (%d)\n", strerror(errno), errno);
1190 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
1191 ast_odbc_release_obj(obj);
1194 res = SQLGetData(stmt, x + 1, SQL_BINARY, fdm, CHUNKSIZE, NULL);
1195 munmap(fdm, CHUNKSIZE);
1196 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1197 ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
1199 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
1200 ast_odbc_release_obj(obj);
1205 truncate(full_fn, fdlen);
1208 res = SQLGetData(stmt, x + 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
1209 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1210 ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
1211 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1212 ast_odbc_release_obj(obj);
1215 if (strcasecmp(coltitle, "msgnum") && strcasecmp(coltitle, "dir") && f)
1216 fprintf(f, "%s=%s\n", coltitle, rowdata);
1219 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1220 ast_odbc_release_obj(obj);
1222 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
1231 static int remove_file(char *dir, int msgnum)
1234 char full_fn[PATH_MAX];
1238 snprintf(msgnums, sizeof(msgnums), "%d", msgnum);
1239 make_file(fn, sizeof(fn), dir, msgnum);
1241 ast_copy_string(fn, dir, sizeof(fn));
1242 ast_filedelete(fn, NULL);
1243 if (ast_check_realtime("voicemail_data")) {
1244 ast_destroy_realtime("voicemail_data", "filename", fn, NULL);
1246 snprintf(full_fn, sizeof(full_fn), "%s.txt", fn);
1251 static int last_message_index(struct ast_vm_user *vmu, char *dir)
1259 struct odbc_obj *obj;
1260 obj = ast_odbc_request_obj(odbc_database, 0);
1262 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
1263 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1264 ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
1265 ast_odbc_release_obj(obj);
1268 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir=?",odbc_table);
1269 res = SQLPrepare(stmt, (unsigned char *)sql, SQL_NTS);
1270 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1271 ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql);
1272 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1273 ast_odbc_release_obj(obj);
1276 SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(dir), 0, (void *)dir, 0, NULL);
1277 res = ast_odbc_smart_execute(obj, stmt);
1278 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1279 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
1280 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1281 ast_odbc_release_obj(obj);
1284 res = SQLFetch(stmt);
1285 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1286 ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
1287 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1288 ast_odbc_release_obj(obj);
1291 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
1292 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1293 ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
1294 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1295 ast_odbc_release_obj(obj);
1298 if (sscanf(rowdata, "%d", &x) != 1)
1299 ast_log(LOG_WARNING, "Failed to read message count!\n");
1300 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1301 ast_odbc_release_obj(obj);
1303 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
1308 static int message_exists(char *dir, int msgnum)
1317 struct odbc_obj *obj;
1318 obj = ast_odbc_request_obj(odbc_database, 0);
1320 snprintf(msgnums, sizeof(msgnums), "%d", msgnum);
1321 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
1322 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1323 ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
1324 ast_odbc_release_obj(obj);
1327 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir=? AND msgnum=?",odbc_table);
1328 res = SQLPrepare(stmt, (unsigned char *)sql, SQL_NTS);
1329 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1330 ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql);
1331 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1332 ast_odbc_release_obj(obj);
1335 SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(dir), 0, (void *)dir, 0, NULL);
1336 SQLBindParameter(stmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(msgnums), 0, (void *)msgnums, 0, NULL);
1337 res = ast_odbc_smart_execute(obj, stmt);
1338 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1339 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
1340 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1341 ast_odbc_release_obj(obj);
1344 res = SQLFetch(stmt);
1345 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1346 ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
1347 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1348 ast_odbc_release_obj(obj);
1351 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
1352 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1353 ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
1354 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1355 ast_odbc_release_obj(obj);
1358 if (sscanf(rowdata, "%d", &x) != 1)
1359 ast_log(LOG_WARNING, "Failed to read message count!\n");
1360 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1361 ast_odbc_release_obj(obj);
1363 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
1368 static int count_messages(struct ast_vm_user *vmu, char *dir)
1370 return last_message_index(vmu, dir) + 1;
1373 static void delete_file(char *sdir, int smsg)
1380 struct odbc_obj *obj;
1381 obj = ast_odbc_request_obj(odbc_database, 0);
1383 snprintf(msgnums, sizeof(msgnums), "%d", smsg);
1384 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
1385 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1386 ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
1387 ast_odbc_release_obj(obj);
1390 snprintf(sql, sizeof(sql), "DELETE FROM %s WHERE dir=? AND msgnum=?",odbc_table);
1391 res = SQLPrepare(stmt, (unsigned char *)sql, SQL_NTS);
1392 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1393 ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql);
1394 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1395 ast_odbc_release_obj(obj);
1398 SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(sdir), 0, (void *)sdir, 0, NULL);
1399 SQLBindParameter(stmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(msgnums), 0, (void *)msgnums, 0, NULL);
1400 res = ast_odbc_smart_execute(obj, stmt);
1401 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1402 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
1403 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1404 ast_odbc_release_obj(obj);
1407 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1408 ast_odbc_release_obj(obj);
1410 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
1415 static void copy_file(char *sdir, int smsg, char *ddir, int dmsg, char *dmailboxuser, char *dmailboxcontext)
1422 struct odbc_obj *obj;
1424 delete_file(ddir, dmsg);
1425 obj = ast_odbc_request_obj(odbc_database, 0);
1427 snprintf(msgnums, sizeof(msgnums), "%d", smsg);
1428 snprintf(msgnumd, sizeof(msgnumd), "%d", dmsg);
1429 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
1430 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1431 ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
1432 ast_odbc_release_obj(obj);
1435 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);
1436 res = SQLPrepare(stmt, (unsigned char *)sql, SQL_NTS);
1437 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1438 ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql);
1439 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1440 ast_odbc_release_obj(obj);
1443 SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(ddir), 0, (void *)ddir, 0, NULL);
1444 SQLBindParameter(stmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(msgnumd), 0, (void *)msgnumd, 0, NULL);
1445 SQLBindParameter(stmt, 3, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(dmailboxuser), 0, (void *)dmailboxuser, 0, NULL);
1446 SQLBindParameter(stmt, 4, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(dmailboxcontext), 0, (void *)dmailboxcontext, 0, NULL);
1447 SQLBindParameter(stmt, 5, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(sdir), 0, (void *)sdir, 0, NULL);
1448 SQLBindParameter(stmt, 6, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(msgnums), 0, (void *)msgnums, 0, NULL);
1449 res = ast_odbc_smart_execute(obj, stmt);
1450 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1451 ast_log(LOG_WARNING, "SQL Execute error!\n[%s] (You probably don't have MySQL 4.1 or later installed)\n\n", sql);
1452 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1453 ast_odbc_release_obj(obj);
1456 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1457 ast_odbc_release_obj(obj);
1459 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
1464 static int store_file(char *dir, char *mailboxuser, char *mailboxcontext, int msgnum)
1469 void *fdm = MAP_FAILED;
1476 char full_fn[PATH_MAX];
1479 const char *context="", *macrocontext="", *callerid="", *origtime="", *duration="";
1480 const char *category = "";
1481 struct ast_config *cfg=NULL;
1482 struct odbc_obj *obj;
1484 delete_file(dir, msgnum);
1485 obj = ast_odbc_request_obj(odbc_database, 0);
1487 ast_copy_string(fmt, vmfmts, sizeof(fmt));
1488 c = strchr(fmt, '|');
1491 if (!strcasecmp(fmt, "wav49"))
1493 snprintf(msgnums, sizeof(msgnums),"%d", msgnum);
1495 make_file(fn, sizeof(fn), dir, msgnum);
1497 ast_copy_string(fn, dir, sizeof(fn));
1498 snprintf(full_fn, sizeof(full_fn), "%s.txt", fn);
1499 cfg = ast_config_load(full_fn);
1500 snprintf(full_fn, sizeof(full_fn), "%s.%s", fn, fmt);
1501 fd = open(full_fn, O_RDWR);
1503 ast_log(LOG_WARNING, "Open of sound file '%s' failed: %s\n", full_fn, strerror(errno));
1504 ast_odbc_release_obj(obj);
1508 context = ast_variable_retrieve(cfg, "message", "context");
1509 if (!context) context = "";
1510 macrocontext = ast_variable_retrieve(cfg, "message", "macrocontext");
1511 if (!macrocontext) macrocontext = "";
1512 callerid = ast_variable_retrieve(cfg, "message", "callerid");
1513 if (!callerid) callerid = "";
1514 origtime = ast_variable_retrieve(cfg, "message", "origtime");
1515 if (!origtime) origtime = "";
1516 duration = ast_variable_retrieve(cfg, "message", "duration");
1517 if (!duration) duration = "";
1518 category = ast_variable_retrieve(cfg, "message", "category");
1519 if (!category) category = "";
1521 fdlen = lseek(fd, 0, SEEK_END);
1522 lseek(fd, 0, SEEK_SET);
1523 printf("Length is %zd\n", fdlen);
1524 fdm = mmap(NULL, fdlen, PROT_READ | PROT_WRITE, MAP_SHARED,fd, 0);
1525 if (fdm == MAP_FAILED) {
1526 ast_log(LOG_WARNING, "Memory map failed!\n");
1527 ast_odbc_release_obj(obj);
1530 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
1531 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1532 ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
1533 ast_odbc_release_obj(obj);
1536 if (!ast_strlen_zero(category))
1537 snprintf(sql, sizeof(sql), "INSERT INTO %s (dir,msgnum,recording,context,macrocontext,callerid,origtime,duration,mailboxuser,mailboxcontext,category) VALUES (?,?,?,?,?,?,?,?,?,?,?)",odbc_table);
1539 snprintf(sql, sizeof(sql), "INSERT INTO %s (dir,msgnum,recording,context,macrocontext,callerid,origtime,duration,mailboxuser,mailboxcontext) VALUES (?,?,?,?,?,?,?,?,?,?)",odbc_table);
1540 res = SQLPrepare(stmt, (unsigned char *)sql, SQL_NTS);
1541 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1542 ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql);
1543 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1544 ast_odbc_release_obj(obj);
1547 len = fdlen; /* SQL_LEN_DATA_AT_EXEC(fdlen); */
1548 SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(dir), 0, (void *)dir, 0, NULL);
1549 SQLBindParameter(stmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(msgnums), 0, (void *)msgnums, 0, NULL);
1550 SQLBindParameter(stmt, 3, SQL_PARAM_INPUT, SQL_C_BINARY, SQL_LONGVARBINARY, fdlen, 0, (void *)fdm, fdlen, &len);
1551 SQLBindParameter(stmt, 4, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(context), 0, (void *)context, 0, NULL);
1552 SQLBindParameter(stmt, 5, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(macrocontext), 0, (void *)macrocontext, 0, NULL);
1553 SQLBindParameter(stmt, 6, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(callerid), 0, (void *)callerid, 0, NULL);
1554 SQLBindParameter(stmt, 7, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(origtime), 0, (void *)origtime, 0, NULL);
1555 SQLBindParameter(stmt, 8, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(duration), 0, (void *)duration, 0, NULL);
1556 SQLBindParameter(stmt, 9, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(mailboxuser), 0, (void *)mailboxuser, 0, NULL);
1557 SQLBindParameter(stmt, 10, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(mailboxcontext), 0, (void *)mailboxcontext, 0, NULL);
1558 if (!ast_strlen_zero(category))
1559 SQLBindParameter(stmt, 11, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(category), 0, (void *)category, 0, NULL);
1560 res = ast_odbc_smart_execute(obj, stmt);
1561 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1562 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
1563 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1564 ast_odbc_release_obj(obj);
1567 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1568 ast_odbc_release_obj(obj);
1570 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
1573 ast_config_destroy(cfg);
1574 if (fdm != MAP_FAILED)
1581 static void rename_file(char *sdir, int smsg, char *mailboxuser, char *mailboxcontext, char *ddir, int dmsg)
1588 struct odbc_obj *obj;
1590 delete_file(ddir, dmsg);
1591 obj = ast_odbc_request_obj(odbc_database, 0);
1593 snprintf(msgnums, sizeof(msgnums), "%d", smsg);
1594 snprintf(msgnumd, sizeof(msgnumd), "%d", dmsg);
1595 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
1596 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1597 ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
1598 ast_odbc_release_obj(obj);
1601 snprintf(sql, sizeof(sql), "UPDATE %s SET dir=?, msgnum=?, mailboxuser=?, mailboxcontext=? WHERE dir=? AND msgnum=?",odbc_table);
1602 res = SQLPrepare(stmt, (unsigned char *)sql, SQL_NTS);
1603 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1604 ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql);
1605 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1606 ast_odbc_release_obj(obj);
1609 SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(ddir), 0, (void *)ddir, 0, NULL);
1610 SQLBindParameter(stmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(msgnumd), 0, (void *)msgnumd, 0, NULL);
1611 SQLBindParameter(stmt, 3, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(mailboxuser), 0, (void *)mailboxuser, 0, NULL);
1612 SQLBindParameter(stmt, 4, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(mailboxcontext), 0, (void *)mailboxcontext, 0, NULL);
1613 SQLBindParameter(stmt, 5, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(sdir), 0, (void *)sdir, 0, NULL);
1614 SQLBindParameter(stmt, 6, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(msgnums), 0, (void *)msgnums, 0, NULL);
1615 res = ast_odbc_smart_execute(obj, stmt);
1616 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1617 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
1618 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1619 ast_odbc_release_obj(obj);
1622 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1623 ast_odbc_release_obj(obj);
1625 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
1631 #ifndef IMAP_STORAGE
1632 static int count_messages(struct ast_vm_user *vmu, char *dir)
1634 /* Find all .txt files - even if they are not in sequence from 0000 */
1638 struct dirent *vment = NULL;
1640 if (vm_lock_path(dir))
1641 return ERROR_LOCK_PATH;
1643 if ((vmdir = opendir(dir))) {
1644 while ((vment = readdir(vmdir))) {
1645 if (strlen(vment->d_name) > 7 && !strncmp(vment->d_name + 7, ".txt", 4))
1650 ast_unlock_path(dir);
1655 static void rename_file(char *sfn, char *dfn)
1657 char stxt[PATH_MAX];
1658 char dtxt[PATH_MAX];
1659 ast_filerename(sfn,dfn,NULL);
1660 snprintf(stxt, sizeof(stxt), "%s.txt", sfn);
1661 snprintf(dtxt, sizeof(dtxt), "%s.txt", dfn);
1662 if (ast_check_realtime("voicemail_data")) {
1663 ast_update_realtime("voicemail_data", "filename", sfn, "filename", dfn, NULL);
1668 static int copy(char *infile, char *outfile)
1676 #ifdef HARDLINK_WHEN_POSSIBLE
1677 /* Hard link if possible; saves disk space & is faster */
1678 if (link(infile, outfile)) {
1680 if ((ifd = open(infile, O_RDONLY)) < 0) {
1681 ast_log(LOG_WARNING, "Unable to open %s in read-only mode\n", infile);
1684 if ((ofd = open(outfile, O_WRONLY | O_TRUNC | O_CREAT, VOICEMAIL_FILE_MODE)) < 0) {
1685 ast_log(LOG_WARNING, "Unable to open %s in write-only mode\n", outfile);
1690 len = read(ifd, buf, sizeof(buf));
1692 ast_log(LOG_WARNING, "Read failed on %s: %s\n", infile, strerror(errno));
1698 res = write(ofd, buf, len);
1699 if (errno == ENOMEM || errno == ENOSPC || res != len) {
1700 ast_log(LOG_WARNING, "Write failed on %s (%d of %d): %s\n", outfile, res, len, strerror(errno));
1710 #ifdef HARDLINK_WHEN_POSSIBLE
1712 /* Hard link succeeded */
1718 static void copy_file(char *frompath, char *topath)
1720 char frompath2[PATH_MAX], topath2[PATH_MAX];
1721 struct ast_variable *tmp,*var = NULL;
1722 char *origmailbox = NULL, *context = NULL, *macrocontext = NULL, *exten = NULL, *priority = NULL, *callerchan = NULL, *callerid = NULL, *origdate = NULL, *origtime = NULL, *category = NULL, *duration = NULL;
1723 ast_filecopy(frompath, topath, NULL);
1724 snprintf(frompath2, sizeof(frompath2), "%s.txt", frompath);
1725 snprintf(topath2, sizeof(topath2), "%s.txt", topath);
1726 if (ast_check_realtime("voicemail_data")) {
1727 var = ast_load_realtime("voicemail_data", "filename", frompath, NULL);
1728 /* This cycle converts ast_variable linked list, to va_list list of arguments, may be there is a better way to do it? */
1729 for (tmp = var; tmp; tmp = tmp->next) {
1730 if (!strcasecmp(tmp->name, "origmailbox")) {
1731 origmailbox = tmp->value;
1732 } else if (!strcasecmp(tmp->name, "context")) {
1733 context = tmp->value;
1734 } else if (!strcasecmp(tmp->name, "macrocontext")) {
1735 macrocontext = tmp->value;
1736 } else if (!strcasecmp(tmp->name, "exten")) {
1738 } else if (!strcasecmp(tmp->name, "priority")) {
1739 priority = tmp->value;
1740 } else if (!strcasecmp(tmp->name, "callerchan")) {
1741 callerchan = tmp->value;
1742 } else if (!strcasecmp(tmp->name, "callerid")) {
1743 callerid = tmp->value;
1744 } else if (!strcasecmp(tmp->name, "origdate")) {
1745 origdate = tmp->value;
1746 } else if (!strcasecmp(tmp->name, "origtime")) {
1747 origtime = tmp->value;
1748 } else if (!strcasecmp(tmp->name, "category")) {
1749 category = tmp->value;
1750 } else if (!strcasecmp(tmp->name, "duration")) {
1751 duration = tmp->value;
1754 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);
1756 copy(frompath2, topath2);
1757 ast_variables_destroy(var);
1762 * A negative return value indicates an error.
1763 * \note Should always be called with a lock already set on dir.
1765 static int last_message_index(struct ast_vm_user *vmu, char *dir)
1768 unsigned char map[MAXMSGLIMIT] = "";
1770 struct dirent *msgdirent;
1773 /* Reading the entire directory into a file map scales better than
1774 * doing a stat repeatedly on a predicted sequence. I suspect this
1775 * is partially due to stat(2) internally doing a readdir(2) itself to
1776 * find each file. */
1777 msgdir = opendir(dir);
1778 while ((msgdirent = readdir(msgdir))) {
1779 if (sscanf(msgdirent->d_name, "msg%d", &msgdirint) == 1 && msgdirint < MAXMSGLIMIT)
1784 for (x = 0; x < vmu->maxmsg; x++) {
1794 static int vm_delete(char *file)
1799 txtsize = (strlen(file) + 5)*sizeof(char);
1800 txt = alloca(txtsize);
1801 /* Sprintf here would safe because we alloca'd exactly the right length,
1802 * but trying to eliminate all sprintf's anyhow
1804 if (ast_check_realtime("voicemail_data")) {
1805 ast_destroy_realtime("voicemail_data", "filename", file, NULL);
1807 snprintf(txt, txtsize, "%s.txt", file);
1809 return ast_filedelete(file, NULL);
1812 static int inbuf(struct baseio *bio, FILE *fi)
1819 if ((l = fread(bio->iobuf,1,BASEMAXINLINE,fi)) <= 0) {
1833 static int inchar(struct baseio *bio, FILE *fi)
1835 if (bio->iocp>=bio->iolen) {
1836 if (!inbuf(bio, fi))
1840 return bio->iobuf[bio->iocp++];
1843 static int ochar(struct baseio *bio, int c, FILE *so)
1845 if (bio->linelength >= BASELINELEN) {
1846 if (fputs(eol,so) == EOF)
1852 if (putc(((unsigned char)c),so) == EOF)
1860 static int base_encode(char *filename, FILE *so)
1862 unsigned char dtable[BASEMAXINLINE];
1867 memset(&bio, 0, sizeof(bio));
1868 bio.iocp = BASEMAXINLINE;
1870 if (!(fi = fopen(filename, "rb"))) {
1871 ast_log(LOG_WARNING, "Failed to open log file: %s: %s\n", filename, strerror(errno));
1875 for (i= 0; i<9; i++) {
1878 dtable[26+i]= 'a'+i;
1879 dtable[26+i+9]= 'j'+i;
1881 for (i= 0; i<8; i++) {
1882 dtable[i+18]= 'S'+i;
1883 dtable[26+i+18]= 's'+i;
1885 for (i= 0; i<10; i++) {
1886 dtable[52+i]= '0'+i;
1892 unsigned char igroup[3], ogroup[4];
1895 igroup[0]= igroup[1]= igroup[2]= 0;
1897 for (n= 0;n<3;n++) {
1898 if ((c = inchar(&bio, fi)) == EOF) {
1903 igroup[n]= (unsigned char)c;
1907 ogroup[0]= dtable[igroup[0]>>2];
1908 ogroup[1]= dtable[((igroup[0]&3)<<4) | (igroup[1]>>4)];
1909 ogroup[2]= dtable[((igroup[1]&0xF)<<2) | (igroup[2]>>6)];
1910 ogroup[3]= dtable[igroup[2]&0x3F];
1920 ochar(&bio, ogroup[i], so);
1924 if (fputs(eol,so) == EOF)
1932 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)
1935 /* Prepare variables for substitution in email body and subject */
1936 pbx_builtin_setvar_helper(ast, "VM_NAME", vmu->fullname);
1937 pbx_builtin_setvar_helper(ast, "VM_DUR", dur);
1938 snprintf(passdata, passdatasize, "%d", msgnum);
1939 pbx_builtin_setvar_helper(ast, "VM_MSGNUM", passdata);
1940 pbx_builtin_setvar_helper(ast, "VM_CONTEXT", context);
1941 pbx_builtin_setvar_helper(ast, "VM_MAILBOX", mailbox);
1942 pbx_builtin_setvar_helper(ast, "VM_CALLERID", ast_callerid_merge(callerid, sizeof(callerid), cidname, cidnum, "Unknown Caller"));
1943 pbx_builtin_setvar_helper(ast, "VM_CIDNAME", (cidname ? cidname : "an unknown caller"));
1944 pbx_builtin_setvar_helper(ast, "VM_CIDNUM", (cidnum ? cidnum : "an unknown caller"));
1945 pbx_builtin_setvar_helper(ast, "VM_DATE", date);
1946 pbx_builtin_setvar_helper(ast, "VM_CATEGORY", category ? ast_strdupa(category) : "no category");
1949 static char *quote(const char *from, char *to, size_t len)
1953 for (; ptr < to + len - 1; from++) {
1956 else if (*from == '\0')
1960 if (ptr < to + len - 1)
1967 * fill in *tm for current time according to the proper timezone, if any.
1968 * Return tm so it can be used as a function argument.
1970 static const struct tm *vmu_tm(const struct ast_vm_user *vmu, struct tm *tm)
1972 const struct vm_zone *z = NULL;
1973 time_t t = time(NULL);
1975 /* Does this user have a timezone specified? */
1976 if (!ast_strlen_zero(vmu->zonetag)) {
1977 /* Find the zone in the list */
1978 AST_LIST_LOCK(&zones);
1979 AST_LIST_TRAVERSE(&zones, z, list) {
1980 if (!strcmp(z->name, vmu->zonetag))
1983 AST_LIST_UNLOCK(&zones);
1985 ast_localtime(&t, tm, z ? z->timezone : NULL);
1989 /*! \brief same as mkstemp, but return a FILE * */
1990 static FILE *vm_mkftemp(char *template)
1993 int pfd = mkstemp(template);
1995 p = fdopen(pfd, "w+");
2004 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)
2007 char host[MAXHOSTNAMELEN] = "";
2015 size_t len_passdata;
2016 char *greeting_attachment;
2024 gethostname(host, sizeof(host)-1);
2025 if (strchr(srcemail, '@'))
2026 ast_copy_string(who, srcemail, sizeof(who));
2028 snprintf(who, sizeof(who), "%s@%s", srcemail, host);
2030 greeting_attachment = strrchr(ast_strdupa(attach), '/');
2031 if (greeting_attachment)
2032 *greeting_attachment++ = '\0';
2034 snprintf(dur, sizeof(dur), "%d:%02d", duration / 60, duration % 60);
2035 strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", vmu_tm(vmu, &tm));
2036 fprintf(p, "Date: %s" ENDL, date);
2038 /* Set date format for voicemail mail */
2039 strftime(date, sizeof(date), emaildateformat, &tm);
2041 if (!ast_strlen_zero(fromstring)) {
2042 struct ast_channel *ast;
2043 if ((ast = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, 0))) {
2045 int vmlen = strlen(fromstring)*3 + 200;
2046 if ((passdata = alloca(vmlen))) {
2047 memset(passdata, 0, vmlen);
2048 prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category);
2049 pbx_substitute_variables_helper(ast, fromstring, passdata, vmlen);
2050 len_passdata = strlen(passdata) * 2 + 3;
2051 passdata2 = alloca(len_passdata);
2052 fprintf(p, "From: %s <%s>" ENDL, quote(passdata, passdata2, len_passdata), who);
2054 ast_log(LOG_WARNING, "Cannot allocate workspace for variable substitution\n");
2055 ast_channel_free(ast);
2057 ast_log(LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
2059 fprintf(p, "From: Asterisk PBX <%s>" ENDL, who);
2060 len_passdata = strlen(vmu->fullname) * 2 + 3;
2061 passdata2 = alloca(len_passdata);
2062 fprintf(p, "To: %s <%s>" ENDL, quote(vmu->fullname, passdata2, len_passdata), vmu->email);
2063 if (!ast_strlen_zero(emailsubject)) {
2064 struct ast_channel *ast;
2065 if ((ast = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, 0))) {
2067 int vmlen = strlen(emailsubject) * 3 + 200;
2068 if ((passdata = alloca(vmlen))) {
2069 memset(passdata, 0, vmlen);
2070 prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category);
2071 pbx_substitute_variables_helper(ast, emailsubject, passdata, vmlen);
2072 fprintf(p, "Subject: %s" ENDL, passdata);
2074 ast_log(LOG_WARNING, "Cannot allocate workspace for variable substitution\n");
2075 ast_channel_free(ast);
2077 ast_log(LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
2078 } else if (!ast_strlen_zero(emailtitle)) {
2079 fprintf(p, emailtitle, msgnum + 1, mailbox) ;
2081 } else if (ast_test_flag((&globalflags), VM_PBXSKIP))
2082 fprintf(p, "Subject: New message %d in mailbox %s" ENDL, msgnum + 1, mailbox);
2084 fprintf(p, "Subject: [PBX]: New message %d in mailbox %s" ENDL, msgnum + 1, mailbox);
2085 fprintf(p, "Message-ID: <Asterisk-%d-%d-%s-%d@%s>" ENDL, msgnum + 1, (unsigned int)ast_random(), mailbox, getpid(), host);
2087 /* additional information needed for IMAP searching */
2088 fprintf(p, "X-Asterisk-VM-Message-Num: %d" ENDL, msgnum + 1);
2089 /* fprintf(p, "X-Asterisk-VM-Orig-Mailbox: %s" ENDL, ext); */
2090 fprintf(p, "X-Asterisk-VM-Server-Name: %s" ENDL, fromstring);
2091 fprintf(p, "X-Asterisk-VM-Context: %s" ENDL, context);
2092 fprintf(p, "X-Asterisk-VM-Extension: %s" ENDL, mailbox);
2093 fprintf(p, "X-Asterisk-VM-Priority: %d" ENDL, chan->priority);
2094 fprintf(p, "X-Asterisk-VM-Caller-channel: %s" ENDL, chan->name);
2095 fprintf(p, "X-Asterisk-VM-Caller-ID-Num: %s" ENDL, cidnum);
2096 fprintf(p, "X-Asterisk-VM-Caller-ID-Name: %s" ENDL, cidname);
2097 fprintf(p, "X-Asterisk-VM-Duration: %d" ENDL, duration);
2098 if (!ast_strlen_zero(category))
2099 fprintf(p, "X-Asterisk-VM-Category: %s" ENDL, category);
2100 fprintf(p, "X-Asterisk-VM-Message-Type: %s\n", msgnum > -1 ? "Message" : greeting_attachment);
2101 fprintf(p, "X-Asterisk-VM-Orig-date: %s" ENDL, date);
2102 fprintf(p, "X-Asterisk-VM-Orig-time: %ld" ENDL, (long)time(NULL));
2104 if (!ast_strlen_zero(cidnum))
2105 fprintf(p, "X-Asterisk-CallerID: %s" ENDL, cidnum);
2106 if (!ast_strlen_zero(cidname))
2107 fprintf(p, "X-Asterisk-CallerIDName: %s" ENDL, cidname);
2108 fprintf(p, "MIME-Version: 1.0" ENDL);
2109 if (attach_user_voicemail) {
2110 /* Something unique. */
2111 snprintf(bound, sizeof(bound), "----voicemail_%d%s%d%d", msgnum + 1, mailbox, getpid(), (unsigned int)ast_random());
2113 fprintf(p, "Content-Type: multipart/mixed; boundary=\"%s\"" ENDL, bound);
2114 fprintf(p, ENDL ENDL "This is a multi-part message in MIME format." ENDL ENDL);
2115 fprintf(p, "--%s" ENDL, bound);
2117 fprintf(p, "Content-Type: text/plain; charset=%s" ENDL "Content-Transfer-Encoding: 8bit" ENDL ENDL, charset);
2119 struct ast_channel *ast;
2120 if ((ast = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, 0))) {
2122 int vmlen = strlen(emailbody)*3 + 200;
2123 if ((passdata = alloca(vmlen))) {
2124 memset(passdata, 0, vmlen);
2125 prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category);
2126 pbx_substitute_variables_helper(ast, emailbody, passdata, vmlen);
2127 fprintf(p, "%s" ENDL, passdata);
2129 ast_log(LOG_WARNING, "Cannot allocate workspace for variable substitution\n");
2130 ast_channel_free(ast);
2132 ast_log(LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
2133 } else if (msgnum > -1){
2134 fprintf(p, "Dear %s:" ENDL ENDL "\tJust wanted to let you know you were just left a %s long message (number %d)" ENDL
2136 "in mailbox %s from %s, on %s so you might" ENDL
2137 "want to check it when you get a chance. Thanks!" ENDL ENDL "\t\t\t\t--Asterisk" ENDL ENDL, vmu->fullname,
2138 dur, msgnum + 1, mailbox, (cidname ? cidname : (cidnum ? cidnum : "an unknown caller")), date);
2140 fprintf(p, "This message is to let you know that your greeting was changed on %s." ENDL
2141 "Please do not delete this message, lest your greeting vanish with it." ENDL ENDL, date);
2143 if (attach_user_voicemail) {
2144 /* Eww. We want formats to tell us their own MIME type */
2145 char *ctype = (!strcasecmp(format, "ogg")) ? "application/" : "audio/x-";
2146 char tmpdir[256], newtmp[256];
2149 if (vmu->volgain < -.001 || vmu->volgain > .001) {
2150 create_dirpath(tmpdir, sizeof(tmpdir), vmu->context, vmu->mailbox, "tmp");
2151 snprintf(newtmp, sizeof(newtmp), "%s/XXXXXX", tmpdir);
2152 tmpfd = mkstemp(newtmp);
2153 ast_debug(3, "newtmp: %s\n", newtmp);
2155 snprintf(tmpcmd, sizeof(tmpcmd), "sox -v %.4f %s.%s %s.%s", vmu->volgain, attach, format, newtmp, format);
2156 ast_safe_system(tmpcmd);
2158 ast_debug(3, "VOLGAIN: Stored at: %s.%s - Level: %.4f - Mailbox: %s\n", attach, format, vmu->volgain, mailbox);
2161 fprintf(p, "--%s" ENDL, bound);
2163 fprintf(p, "Content-Type: %s%s; name=\"msg%04d.%s\"" ENDL, ctype, format, msgnum + 1, format);
2165 fprintf(p, "Content-Type: %s%s; name=\"%s.%s\"" ENDL, ctype, format, greeting_attachment, format);
2166 fprintf(p, "Content-Transfer-Encoding: base64" ENDL);
2167 fprintf(p, "Content-Description: Voicemail sound attachment." ENDL);
2169 fprintf(p, "Content-Disposition: attachment; filename=\"msg%04d.%s\"" ENDL ENDL, msgnum + 1, format);
2171 fprintf(p, "Content-Disposition: attachment; filename=\"%s.%s\"" ENDL ENDL, greeting_attachment, format);
2172 snprintf(fname, sizeof(fname), "%s.%s", attach, format);
2173 base_encode(fname, p);
2174 fprintf(p, ENDL "--%s--" ENDL "." ENDL, bound);
2184 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)
2187 char tmp[80] = "/tmp/astmail-XXXXXX";
2190 if (vmu && ast_strlen_zero(vmu->email)) {
2191 ast_log(LOG_WARNING, "E-mail address missing for mailbox [%s]. E-mail will not be sent.\n", vmu->mailbox);
2194 if (!strcmp(format, "wav49"))
2196 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));
2197 /* Make a temporary file instead of piping directly to sendmail, in case the mail
2199 if ((p = vm_mkftemp(tmp)) == NULL) {
2200 ast_log(LOG_WARNING, "Unable to launch '%s' (can't create temporary file)\n", mailcmd);
2203 make_email_file(p, srcemail, vmu, msgnum, context, mailbox, cidnum, cidname, attach, format, duration, attach_user_voicemail, chan, category, 0);
2205 snprintf(tmp2, sizeof(tmp2), "( %s < %s ; rm -f %s ) &", mailcmd, tmp, tmp);
2206 ast_safe_system(tmp2);
2207 ast_debug(1, "Sent mail to %s with command '%s'\n", vmu->email, mailcmd);
2212 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)
2215 char host[MAXHOSTNAMELEN] = "";
2218 char tmp[80] = "/tmp/astmail-XXXXXX";
2219 char tmp2[PATH_MAX];
2223 if ((p = vm_mkftemp(tmp)) == NULL) {
2224 ast_log(LOG_WARNING, "Unable to launch '%s' (can't create temporary file)\n", mailcmd);
2227 gethostname(host, sizeof(host)-1);
2228 if (strchr(srcemail, '@'))
2229 ast_copy_string(who, srcemail, sizeof(who));
2231 snprintf(who, sizeof(who), "%s@%s", srcemail, host);
2232 snprintf(dur, sizeof(dur), "%d:%02d", duration / 60, duration % 60);
2233 strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", vmu_tm(vmu, &tm));
2234 fprintf(p, "Date: %s\n", date);
2236 if (*pagerfromstring) {
2237 struct ast_channel *ast;
2238 if ((ast = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, 0))) {
2240 int vmlen = strlen(fromstring)*3 + 200;
2241 if ((passdata = alloca(vmlen))) {
2242 memset(passdata, 0, vmlen);
2243 prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category);
2244 pbx_substitute_variables_helper(ast, pagerfromstring, passdata, vmlen);
2245 fprintf(p, "From: %s <%s>\n", passdata, who);
2247 ast_log(LOG_WARNING, "Cannot allocate workspace for variable substitution\n");
2248 ast_channel_free(ast);
2250 ast_log(LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
2252 fprintf(p, "From: Asterisk PBX <%s>\n", who);
2253 fprintf(p, "To: %s\n", pager);
2255 struct ast_channel *ast;
2256 if ((ast = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, 0))) {
2258 int vmlen = strlen(pagersubject) * 3 + 200;
2259 if ((passdata = alloca(vmlen))) {
2260 memset(passdata, 0, vmlen);
2261 prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category);
2262 pbx_substitute_variables_helper(ast, pagersubject, passdata, vmlen);
2263 fprintf(p, "Subject: %s\n\n", passdata);
2265 ast_log(LOG_WARNING, "Cannot allocate workspace for variable substitution\n");
2266 ast_channel_free(ast);
2268 ast_log(LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
2270 fprintf(p, "Subject: New VM\n\n");
2272 strftime(date, sizeof(date), "%A, %B %d, %Y at %r", &tm);
2274 struct ast_channel *ast;
2275 if ((ast = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, 0))) {
2277 int vmlen = strlen(pagerbody) * 3 + 200;
2278 passdata = alloca(vmlen);
2279 memset(passdata, 0, vmlen);
2280 prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category);
2281 pbx_substitute_variables_helper(ast, pagerbody, passdata, vmlen);
2282 fprintf(p, "%s\n", passdata);
2283 ast_channel_free(ast);
2285 ast_log(LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
2287 fprintf(p, "New %s long msg in box %s\n"
2288 "from %s, on %s", dur, mailbox, (cidname ? cidname : (cidnum ? cidnum : "unknown")), date);
2291 snprintf(tmp2, sizeof(tmp2), "( %s < %s ; rm -f %s ) &", mailcmd, tmp, tmp);
2292 ast_safe_system(tmp2);
2293 ast_debug(1, "Sent page to %s with command '%s'\n", pager, mailcmd);
2297 static int get_date(char *s, int len)
2302 ast_localtime(&t, &tm, NULL);
2303 return strftime(s, len, "%a %b %e %r %Z %Y", &tm);
2306 static int invent_message(struct ast_channel *chan, char *context, char *ext, int busy, char *ecodes)
2310 char dest[PATH_MAX];
2312 snprintf(fn, sizeof(fn), "%s%s/%s/greet", VM_SPOOL_DIR, context, ext);
2314 if ((res = create_dirpath(dest, sizeof(dest), context, ext, ""))) {
2315 ast_log(LOG_WARNING, "Failed to make directory(%s)\n", fn);
2319 RETRIEVE(fn, -1, ext, context);
2320 if (ast_fileexists(fn, NULL, NULL) > 0) {
2321 res = ast_stream_and_wait(chan, fn, ecodes);
2327 /* Dispose just in case */
2329 res = ast_stream_and_wait(chan, "vm-theperson", ecodes);
2332 res = ast_say_digit_str(chan, ext, ecodes, chan->language);
2336 res = ast_stream_and_wait(chan, busy ? "vm-isonphone" : "vm-isunavail", ecodes);
2340 static void free_user(struct ast_vm_user *vmu)
2342 if (!ast_test_flag(vmu, VM_ALLOCED))
2348 static void free_zone(struct vm_zone *z)
2353 static const char *mbox(int id)
2355 static const char *msgs[] = {
2367 return (id >= 0 && id < (sizeof(msgs)/sizeof(msgs[0]))) ? msgs[id] : "Unknown";
2371 static int inboxcount(const char *mailbox, int *newmsgs, int *oldmsgs)
2378 char tmp[PATH_MAX] = "";
2379 struct odbc_obj *obj;
2387 /* If no mailbox, return immediately */
2388 if (ast_strlen_zero(mailbox))
2391 ast_copy_string(tmp, mailbox, sizeof(tmp));
2393 context = strchr(tmp, '@');
2398 context = "default";
2400 obj = ast_odbc_request_obj(odbc_database, 0);
2402 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
2403 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2404 ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
2405 ast_odbc_release_obj(obj);
2408 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir = '%s%s/%s/%s'", odbc_table, VM_SPOOL_DIR, context, tmp, "INBOX");
2409 res = SQLPrepare(stmt, (unsigned char *)sql, SQL_NTS);
2410 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2411 ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql);
2412 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2413 ast_odbc_release_obj(obj);
2416 res = ast_odbc_smart_execute(obj, stmt);
2417 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2418 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
2419 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2420 ast_odbc_release_obj(obj);
2423 res = SQLFetch(stmt);
2424 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2425 ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
2426 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2427 ast_odbc_release_obj(obj);
2430 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
2431 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2432 ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
2433 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2434 ast_odbc_release_obj(obj);
2437 *newmsgs = atoi(rowdata);
2438 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2440 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
2441 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2442 ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
2443 ast_odbc_release_obj(obj);
2446 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir = '%s%s/%s/%s'", odbc_table, VM_SPOOL_DIR, context, tmp, "Old");
2447 res = SQLPrepare(stmt, (unsigned char *)sql, SQL_NTS);
2448 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2449 ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql);
2450 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2451 ast_odbc_release_obj(obj);
2454 res = ast_odbc_smart_execute(obj, stmt);
2455 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2456 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
2457 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2458 ast_odbc_release_obj(obj);
2461 res = SQLFetch(stmt);
2462 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2463 ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
2464 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2465 ast_odbc_release_obj(obj);
2468 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
2469 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2470 ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
2471 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2472 ast_odbc_release_obj(obj);
2475 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2476 ast_odbc_release_obj(obj);
2477 *oldmsgs = atoi(rowdata);
2480 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
2486 static int messagecount(const char *context, const char *mailbox, const char *folder)
2488 struct odbc_obj *obj = NULL;
2491 SQLHSTMT stmt = NULL;
2496 /* If no mailbox, return immediately */
2497 if (ast_strlen_zero(mailbox))
2500 obj = ast_odbc_request_obj(odbc_database, 0);
2502 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
2503 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2504 ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
2507 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir = '%s%s/%s/%s'", odbc_table, VM_SPOOL_DIR, context, mailbox, folder);
2508 res = SQLPrepare(stmt, (unsigned char *)sql, SQL_NTS);
2509 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2510 ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql);
2511 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
2514 res = ast_odbc_smart_execute(obj, stmt);
2515 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2516 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
2517 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2520 res = SQLFetch(stmt);
2521 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2522 ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
2523 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2526 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
2527 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2528 ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
2529 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2532 nummsgs = atoi(rowdata);
2533 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2535 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
2539 ast_odbc_release_obj(obj);
2543 static int has_voicemail(const char *mailbox, const char *folder)
2545 char *context, tmp[256];
2546 ast_copy_string(tmp, mailbox, sizeof(tmp));
2547 if ((context = strchr(tmp, '@')))
2550 context = "default";
2552 if (messagecount(context, tmp, folder))
2558 #elif defined(IMAP_STORAGE)
2560 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)
2562 char *myserveremail = serveremail;
2567 char tmp[80] = "/tmp/astmail-XXXXXX";
2572 /* Attach only the first format */
2573 fmt = ast_strdupa(fmt);
2575 strsep(&stringp, "|");
2577 if (!ast_strlen_zero(vmu->serveremail))
2578 myserveremail = vmu->serveremail;
2581 make_file(fn, sizeof(fn), dir, msgnum);
2583 ast_copy_string (fn, dir, sizeof(fn));
2585 if (ast_strlen_zero(vmu->email))
2586 ast_copy_string(vmu->email, vmu->imapuser, sizeof(vmu->email));
2588 if (!strcmp(fmt, "wav49"))
2590 ast_debug(3, "Storing file '%s', format '%s'\n", fn, fmt);
2592 /* Make a temporary file instead of piping directly to sendmail, in case the mail
2594 if (!(p = vm_mkftemp(tmp))) {
2595 ast_log(LOG_WARNING, "Unable to store '%s' (can't create temporary file)\n", fn);
2600 if (msgnum < 0 && imapgreetings) {
2601 init_mailstream(vms, GREETINGS_FOLDER);
2602 imap_delete_old_greeting(fn, vms);
2605 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);
2606 /* read mail file to memory */
2609 if (!(buf = ast_malloc(len+1))) {
2610 ast_log(LOG_ERROR, "Can't allocate %ld bytes to read message\n", len+1);
2613 fread(buf, len, 1, p);
2614 ((char *)buf)[len] = '\0';
2615 INIT(&str, mail_string, buf, len);
2616 init_mailstream(vms, NEW_FOLDER);
2617 imap_mailbox_name(mailbox, vms, NEW_FOLDER, 1);
2618 if(!mail_append(vms->mailstream, mailbox, &str))
2619 ast_log(LOG_ERROR, "Error while sending the message to %s\n", mailbox);
2623 ast_debug(3, "%s stored\n", fn);
2628 static int inboxcount(const char *mailbox, int *newmsgs, int *oldmsgs)
2633 struct ast_vm_user *vmu;
2634 struct vm_state *vms_p;
2647 ast_debug(3,"Mailbox is set to %s\n",mailbox);
2649 /* If no mailbox, return immediately */
2650 if (ast_strlen_zero(mailbox))
2653 if (strchr(mailbox, ',')) {
2655 ast_copy_string(tmp, mailbox, sizeof(tmp));
2658 while((cur = strsep(&mb, ", "))) {
2659 if (!ast_strlen_zero(cur)) {
2660 if (inboxcount(cur, newmsgs ? &tmpnew : NULL, oldmsgs ? &tmpold : NULL))
2673 ast_copy_string(tmp, mailbox, sizeof(tmp));
2675 if ((context = strchr(tmp, '@'))) {
2680 context = "default";
2681 mailboxnc = (char *)mailbox;
2684 /* We have to get the user before we can open the stream! */
2685 /*ast_debug(1,"Before find_user, context is %s and mailbox is %s\n",context,mailbox); */
2686 if (!(vmu = find_user(NULL, context, mailboxnc))) {
2687 ast_log(LOG_ERROR, "Couldn't find mailbox %s in context %s\n", mailboxnc, context);
2691 /* No IMAP account available */
2692 if (vmu->imapuser[0] == '\0') {
2693 ast_log (LOG_WARNING,"IMAP user not set for mailbox %s\n",vmu->mailbox);
2698 /* check if someone is accessing this box right now... */
2699 if ((vms_p = get_vm_state_by_imapuser(vmu->imapuser, 1)) || (vms_p = get_vm_state_by_mailbox(mailboxnc, 1))) {
2700 ast_debug(3,"Returning before search - user is logged in\n");
2701 *newmsgs = vms_p->newmessages;
2702 *oldmsgs = vms_p->oldmessages;
2707 /* add one if not there... */
2708 if (!(vms_p = get_vm_state_by_imapuser(vmu->imapuser, 0)) && !(vms_p = get_vm_state_by_mailbox(mailboxnc, 0))) {
2709 ast_debug(3,"Adding new vmstate for %s\n",vmu->imapuser);
2710 if (!(vms_p = ast_calloc(1, sizeof(*vms_p)))) {
2714 ast_copy_string(vms_p->imapuser,vmu->imapuser, sizeof(vms_p->imapuser));
2715 ast_copy_string(vms_p->username, mailboxnc, sizeof(vms_p->username)); /* save for access from interactive entry point */
2716 vms_p->mailstream = NIL; /* save for access from interactive entry point */
2717 ast_debug(3,"Copied %s to %s\n",vmu->imapuser,vms_p->imapuser);
2719 /* set mailbox to INBOX! */
2720 ast_copy_string(vms_p->curbox, mbox(NEW_FOLDER), sizeof(vms_p->curbox));
2721 init_vm_state(vms_p);
2722 vmstate_insert(vms_p);
2725 /* If no mailstream exists yet and even after attempting to initialize it fails, bail out */
2726 ret = init_mailstream(vms_p, NEW_FOLDER);
2727 if (!vms_p->mailstream) {
2728 ast_log (LOG_ERROR,"Houston we have a problem - IMAP mailstream is NULL\n");
2733 if (!ret && vms_p->updated > 0) {
2735 pgm = mail_newsearchpgm();
2736 hdr = mail_newsearchheader("X-Asterisk-VM-Extension", (char *)mailboxnc);
2742 vms_p->vmArrayIndex = 0;
2743 mail_search_full(vms_p->mailstream, NULL, pgm, NIL);
2744 *newmsgs = vms_p->vmArrayIndex;
2745 vms_p->newmessages = vms_p->vmArrayIndex;
2746 mail_free_searchpgm(&pgm);
2749 pgm = mail_newsearchpgm ();
2750 hdr = mail_newsearchheader ("X-Asterisk-VM-Extension", (char *)mailboxnc);
2756 vms_p->vmArrayIndex = 0;
2757 mail_search_full(vms_p->mailstream, NULL, pgm, NIL);
2758 *oldmsgs = vms_p->vmArrayIndex;
2759 vms_p->oldmessages = vms_p->vmArrayIndex;
2760 mail_free_searchpgm(&pgm);
2764 if (vms_p->updated > 1) { /* changes, so we did the searches above */
2766 } else { /* no changes, so don't search */
2767 mail_ping(vms_p->mailstream);
2768 /* Keep the old data */
2769 *newmsgs = vms_p->newmessages;
2770 *oldmsgs = vms_p->oldmessages;
2777 static int has_voicemail(const char *mailbox, const char *folder)
2779 int newmsgs, oldmsgs;
2781 if(inboxcount(mailbox, &newmsgs, &oldmsgs))
2782 return folder? oldmsgs: newmsgs;
2787 static int messagecount(const char *context, const char *mailbox, const char *folder)
2789 int newmsgs, oldmsgs;
2792 if (ast_strlen_zero(mailbox))
2794 sprintf(tmp,"%s@%s", mailbox, ast_strlen_zero(context)? "default": context);
2796 if(inboxcount(tmp, &newmsgs, &oldmsgs))
2797 return folder? oldmsgs: newmsgs;
2803 #ifndef IMAP_STORAGE
2804 /* copy message only used by file storage */
2805 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)
2807 char fromdir[PATH_MAX], todir[PATH_MAX], frompath[PATH_MAX], topath[PATH_MAX];
2808 const char *frombox = mbox(imbox);
2811 ast_log(LOG_NOTICE, "Copying message from %s@%s to %s@%s\n", vmu->mailbox, vmu->context, recip->mailbox, recip->context);
2813 create_dirpath(todir, sizeof(todir), recip->context, recip->mailbox, "INBOX");
2816 make_dir(fromdir, sizeof(fromdir), vmu->context, vmu->mailbox, frombox);
2818 ast_copy_string(fromdir, dir, sizeof(fromdir));
2820 make_file(frompath, sizeof(frompath), fromdir, msgnum);
2821 make_dir(todir, sizeof(todir), recip->context, recip->mailbox, frombox);
2823 if (vm_lock_path(todir))
2824 return ERROR_LOCK_PATH;
2826 recipmsgnum = last_message_index(recip, todir) + 1;
2827 if (recipmsgnum < recip->maxmsg) {
2828 make_file(topath, sizeof(topath), todir, recipmsgnum);
2829 COPY(fromdir, msgnum, todir, recipmsgnum, recip->mailbox, recip->context, frompath, topath);
2831 ast_log(LOG_ERROR, "Recipient mailbox %s@%s is full\n", recip->mailbox, recip->context);
2833 ast_unlock_path(todir);
2834 notify_new_message(chan, recip, recipmsgnum, duration, fmt, S_OR(chan->cid.cid_num, NULL), S_OR(chan->cid.cid_name, NULL));
2839 #if !(defined(IMAP_STORAGE) || defined(ODBC_STORAGE))
2840 static int messagecount(const char *context, const char *mailbox, const char *folder)
2842 return __has_voicemail(context, mailbox, folder, 0);
2846 static int __has_voicemail(const char *context, const char *mailbox, const char *folder, int shortcircuit)
2853 /* If no mailbox, return immediately */
2854 if (ast_strlen_zero(mailbox))
2857 if (ast_strlen_zero(folder))
2859 if (ast_strlen_zero(context))
2860 context = "default";
2862 snprintf(fn, sizeof(fn), "%s%s/%s/%s", VM_SPOOL_DIR, context, mailbox, folder);
2864 if (!(dir = opendir(fn)))
2867 while ((de = readdir(dir))) {
2868 if (!strncasecmp(de->d_name, "msg", 3)) {
2872 } else if (!strncasecmp(de->d_name + 8, "txt", 3))
2883 static int has_voicemail(const char *mailbox, const char *folder)
2885 char tmp[256], *tmp2 = tmp, *mbox, *context;
2886 ast_copy_string(tmp, mailbox, sizeof(tmp));
2887 while ((mbox = strsep(&tmp2, ","))) {
2888 if ((context = strchr(mbox, '@')))
2891 context = "default";
2892 if (__has_voicemail(context, mbox, folder, 1))
2899 static int inboxcount(const char *mailbox, int *newmsgs, int *oldmsgs)
2904 /* If no mailbox, return immediately */
2905 if (ast_strlen_zero(mailbox))
2913 if (strchr(mailbox, ',')) {
2917 ast_copy_string(tmp, mailbox, sizeof(tmp));
2919 while ((cur = strsep(&mb, ", "))) {
2920 if (!ast_strlen_zero(cur)) {
2921 if (inboxcount(cur, newmsgs ? &tmpnew : NULL, oldmsgs ? &tmpold : NULL))
2934 ast_copy_string(tmp, mailbox, sizeof(tmp));
2936 if ((context = strchr(tmp, '@')))
2939 context = "default";
2942 *newmsgs = __has_voicemail(context, tmp, "INBOX", 0);
2944 *oldmsgs = __has_voicemail(context, tmp, "Old", 0);
2951 static void run_externnotify(char *context, char *extension)
2953 char arguments[255];
2954 char ext_context[256] = "";
2955 int newvoicemails = 0, oldvoicemails = 0;
2956 struct ast_smdi_mwi_message *mwi_msg;
2958 if (!ast_strlen_zero(context))
2959 snprintf(ext_context, sizeof(ext_context), "%s@%s", extension, context);
2961 ast_copy_string(ext_context, extension, sizeof(ext_context));
2964 if (ast_app_has_voicemail(ext_context, NULL))
2965 ast_smdi_mwi_set(smdi_iface, extension);
2967 ast_smdi_mwi_unset(smdi_iface, extension);
2969 if ((mwi_msg = ast_smdi_mwi_message_wait(smdi_iface, SMDI_MWI_WAIT_TIMEOUT))) {
2970 ast_log(LOG_ERROR, "Error executing SMDI MWI change for %s on %s\n", extension, smdi_iface->name);
2971 if (!strncmp(mwi_msg->cause, "INV", 3))
2972 ast_log(LOG_ERROR, "Invalid MWI extension: %s\n", mwi_msg->fwd_st);
2973 else if (!strncmp(mwi_msg->cause, "BLK", 3))
2974 ast_log(LOG_WARNING, "MWI light was already on or off for %s\n", mwi_msg->fwd_st);
2975 ast_log(LOG_WARNING, "The switch reported '%s'\n", mwi_msg->cause);
2976 ASTOBJ_UNREF(mwi_msg, ast_smdi_mwi_message_destroy);
2978 ast_debug(1, "Successfully executed SMDI MWI change for %s on %s\n", extension, smdi_iface->name);
2982 if (!ast_strlen_zero(externnotify)) {
2983 if (inboxcount(ext_context, &newvoicemails, &oldvoicemails)) {
2984 ast_log(LOG_ERROR, "Problem in calculating number of voicemail messages available for extension %s\n", extension);
2986 snprintf(arguments, sizeof(arguments), "%s %s %s %d&", externnotify, context, extension, newvoicemails);
2987 ast_debug(1, "Executing %s\n", arguments);
2988 ast_safe_system(arguments);
2993 struct leave_vm_options {
2995 signed char record_gain;
2998 static int leave_voicemail(struct ast_channel *chan, char *ext, struct leave_vm_options *options)
3001 int newmsgs, oldmsgs;
3002 struct vm_state *vms = NULL;
3004 char txtfile[PATH_MAX], tmptxtfile[PATH_MAX];
3020 char dir[PATH_MAX], tmpdir[PATH_MAX];
3022 char prefile[PATH_MAX] = "";
3023 char tempfile[PATH_MAX] = "";
3024 char ext_context[256] = "";
3027 char ecodes[16] = "#";
3028 char tmp[1024] = "", *tmpptr;
3029 struct ast_vm_user *vmu;
3030 struct ast_vm_user svm;
3031 const char *category = NULL;
3033 ast_copy_string(tmp, ext, sizeof(tmp));
3035 if ((context = strchr(tmp, '@'))) {
3037 tmpptr = strchr(context, '&');
3039 tmpptr = strchr(ext, '&');
3045 category = pbx_builtin_getvar_helper(chan, "VM_CATEGORY");
3047 ast_debug(3, "Before find_user\n");
3048 if (!(vmu = find_user(&svm, context, ext))) {
3049 ast_log(LOG_WARNING, "No entry in voicemail config file for '%s'\n", ext);
3050 pbx_builtin_setvar_helper(chan, "VMSTATUS", "FAILED");
3053 /* Setup pre-file if appropriate */
3054 if (strcmp(vmu->context, "default"))
3055 snprintf(ext_context, sizeof(ext_context), "%s@%s", ext, vmu->context);
3057 ast_copy_string(ext_context, vmu->mailbox, sizeof(ext_context));
3058 if (ast_test_flag(options, OPT_BUSY_GREETING)) {
3059 snprintf(prefile, sizeof(prefile), "%s%s/%s/busy", VM_SPOOL_DIR, vmu->context, ext);
3060 } else if (ast_test_flag(options, OPT_UNAVAIL_GREETING)) {
3061 snprintf(prefile, sizeof(prefile), "%s%s/%s/unavail", VM_SPOOL_DIR, vmu->context, ext);
3063 snprintf(tempfile, sizeof(tempfile), "%s%s/%s/temp", VM_SPOOL_DIR, vmu->context, ext);
3064 if ((res = create_dirpath(tmpdir, sizeof(tmpdir), vmu->context, ext, "tmp"))) {
3065 ast_log(LOG_WARNING, "Failed to make directory (%s)\n", tempfile);
3068 RETRIEVE(tempfile, -1, ext, context);
3069 if (ast_fileexists(tempfile, NULL, NULL) > 0)
3070 ast_copy_string(prefile, tempfile, sizeof(prefile));
3071 DISPOSE(tempfile, -1);
3072 /* It's easier just to try to make it than to check for its existence */