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 authuser[32];
126 static char authpassword[42];
128 static int expungeonhangup = 1;
129 AST_MUTEX_DEFINE_STATIC(delimiter_lock);
130 static char delimiter = '\0';
135 /* Forward declarations for IMAP */
136 static int init_mailstream(struct vm_state *vms, int box);
137 static void write_file(char *filename, char *buffer, unsigned long len);
138 static void display_body(BODY *body, char *pfx, long i);
139 static char *get_header_by_tag(char *header, char *tag, char *buf, size_t len);
140 static void vm_imap_delete(int msgnum, struct vm_state *vms);
141 static char *get_user_by_mailbox(char *mailbox, char *buf, size_t len);
142 static struct vm_state *get_vm_state_by_imapuser(char *user, int interactive);
143 static struct vm_state *get_vm_state_by_mailbox(const char *mailbox, int interactive);
144 static void vmstate_insert(struct vm_state *vms);
145 static void vmstate_delete(struct vm_state *vms);
146 static void set_update(MAILSTREAM * stream);
147 static void init_vm_state(struct vm_state *vms);
148 static void check_msgArray(struct vm_state *vms);
149 static void copy_msgArray(struct vm_state *dst, struct vm_state *src);
150 static int save_body(BODY *body, struct vm_state *vms, char *section, char *format);
151 static int make_gsm_file(char *dest, char *imapuser, char *dir, int num);
152 static void get_mailbox_delimiter(MAILSTREAM *stream);
153 static void mm_parsequota (MAILSTREAM *stream, unsigned char *msg, QUOTALIST *pquota);
154 static void imap_mailbox_name(char *spec, struct vm_state *vms, int box, int target);
155 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);
156 static void update_messages_by_imapuser(const char *user, unsigned long number);
159 struct vm_state *vms;
160 AST_LIST_ENTRY(vmstate) list;
163 static AST_LIST_HEAD_STATIC(vmstates, vmstate);
167 #define SMDI_MWI_WAIT_TIMEOUT 1000 /* 1 second */
169 #define COMMAND_TIMEOUT 5000
170 /* Don't modify these here; set your umask at runtime instead */
171 #define VOICEMAIL_DIR_MODE 0777
172 #define VOICEMAIL_FILE_MODE 0666
173 #define CHUNKSIZE 65536
175 #define VOICEMAIL_CONFIG "voicemail.conf"
176 #define ASTERISK_USERNAME "asterisk"
178 /* Default mail command to mail voicemail. Change it with the
179 mailcmd= command in voicemail.conf */
180 #define SENDMAIL "/usr/sbin/sendmail -t"
182 #define INTRO "vm-intro"
185 #define MAXMSGLIMIT 9999
187 #define BASEMAXINLINE 256
188 #define BASELINELEN 72
189 #define BASEMAXINLINE 256
192 #define MAX_DATETIME_FORMAT 512
193 #define MAX_NUM_CID_CONTEXTS 10
195 #define VM_REVIEW (1 << 0)
196 #define VM_OPERATOR (1 << 1)
197 #define VM_SAYCID (1 << 2)
198 #define VM_SVMAIL (1 << 3)
199 #define VM_ENVELOPE (1 << 4)
200 #define VM_SAYDURATION (1 << 5)
201 #define VM_SKIPAFTERCMD (1 << 6)
202 #define VM_FORCENAME (1 << 7) /*!< Have new users record their name */
203 #define VM_FORCEGREET (1 << 8) /*!< Have new users record their greetings */
204 #define VM_PBXSKIP (1 << 9)
205 #define VM_DIRECFORWARD (1 << 10) /*!< directory_forward */
206 #define VM_ATTACH (1 << 11)
207 #define VM_DELETE (1 << 12)
208 #define VM_ALLOCED (1 << 13)
209 #define VM_SEARCH (1 << 14)
210 #define VM_TEMPGREETWARN (1 << 15) /*!< Remind user tempgreeting is set */
211 #define ERROR_LOCK_PATH -100
215 OPT_SILENT = (1 << 0),
216 OPT_BUSY_GREETING = (1 << 1),
217 OPT_UNAVAIL_GREETING = (1 << 2),
218 OPT_RECORDGAIN = (1 << 3),
219 OPT_PREPEND_MAILBOX = (1 << 4),
220 OPT_AUTOPLAY = (1 << 6),
224 OPT_ARG_RECORDGAIN = 0,
225 OPT_ARG_PLAYFOLDER = 1,
226 /* This *must* be the last value in this enum! */
227 OPT_ARG_ARRAY_SIZE = 2,
230 AST_APP_OPTIONS(vm_app_options, {
231 AST_APP_OPTION('s', OPT_SILENT),
232 AST_APP_OPTION('b', OPT_BUSY_GREETING),
233 AST_APP_OPTION('u', OPT_UNAVAIL_GREETING),
234 AST_APP_OPTION_ARG('g', OPT_RECORDGAIN, OPT_ARG_RECORDGAIN),
235 AST_APP_OPTION('p', OPT_PREPEND_MAILBOX),
236 AST_APP_OPTION_ARG('a', OPT_AUTOPLAY, OPT_ARG_PLAYFOLDER),
239 static int load_config(void);
241 /*! \page vmlang Voicemail Language Syntaxes Supported
243 \par Syntaxes supported, not really language codes.
250 \arg \b pt - Portuguese
251 \arg \b pt_BR - Portuguese (Brazil)
253 \arg \b no - Norwegian
255 \arg \b tw - Chinese (Taiwan)
257 German requires the following additional soundfile:
258 \arg \b 1F einE (feminine)
260 Spanish requires the following additional soundfile:
261 \arg \b 1M un (masculine)
263 Dutch, Portuguese & Spanish require the following additional soundfiles:
264 \arg \b vm-INBOXs singular of 'new'
265 \arg \b vm-Olds singular of 'old/heard/read'
268 \arg \b vm-INBOX nieuwe (nl)
269 \arg \b vm-Old oude (nl)
272 \arg \b vm-new-a 'new', feminine singular accusative
273 \arg \b vm-new-e 'new', feminine plural accusative
274 \arg \b vm-new-ych 'new', feminine plural genitive
275 \arg \b vm-old-a 'old', feminine singular accusative
276 \arg \b vm-old-e 'old', feminine plural accusative
277 \arg \b vm-old-ych 'old', feminine plural genitive
278 \arg \b digits/1-a 'one', not always same as 'digits/1'
279 \arg \b digits/2-ie 'two', not always same as 'digits/2'
282 \arg \b vm-nytt singular of 'new'
283 \arg \b vm-nya plural of 'new'
284 \arg \b vm-gammalt singular of 'old'
285 \arg \b vm-gamla plural of 'old'
286 \arg \b digits/ett 'one', not always same as 'digits/1'
289 \arg \b vm-ny singular of 'new'
290 \arg \b vm-nye plural of 'new'
291 \arg \b vm-gammel singular of 'old'
292 \arg \b vm-gamle plural of 'old'
300 Italian requires the following additional soundfile:
304 \arg \b vm-nuovi new plural
305 \arg \b vm-vecchio old
306 \arg \b vm-vecchi old plural
308 Chinese (Taiwan) requires the following additional soundfile:
309 \arg \b vm-tong A class-word for call (tong1)
310 \arg \b vm-ri A class-word for day (ri4)
311 \arg \b vm-you You (ni3)
312 \arg \b vm-haveno Have no (mei2 you3)
313 \arg \b vm-have Have (you3)
314 \arg \b vm-listen To listen (yao4 ting1)
317 \note Don't use vm-INBOX or vm-Old, because they are the name of the INBOX and Old folders,
318 spelled among others when you have to change folder. For the above reasons, vm-INBOX
319 and vm-Old are spelled plural, to make them sound more as folder name than an adjective.
328 unsigned char iobuf[BASEMAXINLINE];
331 /*! Structure for linked list of users
332 * Use ast_vm_user_destroy() to free one of these structures. */
334 char context[AST_MAX_CONTEXT]; /*!< Voicemail context */
335 char mailbox[AST_MAX_EXTENSION]; /*!< Mailbox id, unique within vm context */
336 char password[80]; /*!< Secret pin code, numbers only */
337 char fullname[80]; /*!< Full name, for directory app */
338 char email[80]; /*!< E-mail address */
339 char pager[80]; /*!< E-mail address to pager (no attachment) */
340 char serveremail[80]; /*!< From: Mail address */
341 char mailcmd[160]; /*!< Configurable mail command */
342 char language[MAX_LANGUAGE]; /*!< Config: Language setting */
343 char zonetag[80]; /*!< Time zone */
346 char uniqueid[20]; /*!< Unique integer identifier */
348 char attachfmt[20]; /*!< Attachment format */
349 unsigned int flags; /*!< VM_ flags */
351 int maxmsg; /*!< Maximum number of msgs per folder for this mailbox */
352 int maxsecs; /*!< Maximum number of seconds per message for this mailbox */
354 char imapuser[80]; /*!< IMAP server login */
355 char imappassword[80]; /*!< IMAP server password if authpassword not defined */
357 double volgain; /*!< Volume gain for voicemails sent via email */
358 AST_LIST_ENTRY(ast_vm_user) list;
361 /*! Voicemail time zones */
363 AST_LIST_ENTRY(vm_zone) list;
366 char msg_format[512];
369 /*! Voicemail mailbox state */
373 char curdir[PATH_MAX];
374 char vmbox[PATH_MAX];
386 int updated; /*!< decremented on each mail check until 1 -allows delay */
388 MAILSTREAM *mailstream;
390 char imapuser[80]; /*!< IMAP server login */
392 unsigned int quota_limit;
393 unsigned int quota_usage;
394 struct vm_state *persist_vms;
400 static char odbc_database[80];
401 static char odbc_table[80];
402 #define RETRIEVE(a,b) retrieve_file(a,b)
403 #define DISPOSE(a,b) remove_file(a,b)
404 #define STORE(a,b,c,d,e,f,g,h,i) store_file(a,b,c,d)
405 #define EXISTS(a,b,c,d) (message_exists(a,b))
406 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(a,b,c,d,e,f))
407 #define COPY(a,b,c,d,e,f,g,h) (copy_file(a,b,c,d,e,f))
408 #define DELETE(a,b,c) (delete_file(a,b))
411 #define RETRIEVE(a,b)
413 #define STORE(a,b,c,d,e,f,g,h,i) (imap_store_file(a,b,c,d,e,f,g,h,i))
414 #define EXISTS(a,b,c,d) (ast_fileexists(c,NULL,d) > 0)
415 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(g,h));
416 #define COPY(a,b,c,d,e,f,g,h) (copy_file(g,h));
417 #define IMAP_DELETE(a,b,c,d) (vm_imap_delete(b,d))
418 #define DELETE(a,b,c) (vm_delete(c))
420 #define RETRIEVE(a,b)
422 #define STORE(a,b,c,d,e,f,g,h,i)
423 #define EXISTS(a,b,c,d) (ast_fileexists(c,NULL,d) > 0)
424 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(g,h));
425 #define COPY(a,b,c,d,e,f,g,h) (copy_file(g,h));
426 #define DELETE(a,b,c) (vm_delete(c))
430 static char VM_SPOOL_DIR[PATH_MAX];
432 static char ext_pass_cmd[128];
434 #define PWDCHANGE_INTERNAL (1 << 1)
435 #define PWDCHANGE_EXTERNAL (1 << 2)
436 static int pwdchange = PWDCHANGE_INTERNAL;
439 #define tdesc "Comedian Mail (Voicemail System) with ODBC Storage"
442 # define tdesc "Comedian Mail (Voicemail System) with IMAP Storage"
444 # define tdesc "Comedian Mail (Voicemail System)"
448 static char userscontext[AST_MAX_EXTENSION] = "default";
450 static char *addesc = "Comedian Mail";
452 static char *synopsis_vm = "Leave a Voicemail message";
454 static char *descrip_vm =
455 " VoiceMail(mailbox[@context][&mailbox[@context]][...][|options]): This\n"
456 "application allows the calling party to leave a message for the specified\n"
457 "list of mailboxes. When multiple mailboxes are specified, the greeting will\n"
458 "be taken from the first mailbox specified. Dialplan execution will stop if the\n"
459 "specified mailbox does not exist.\n"
460 " The Voicemail application will exit if any of the following DTMF digits are\n"
462 " 0 - Jump to the 'o' extension in the current dialplan context.\n"
463 " * - Jump to the 'a' extension in the current dialplan context.\n"
464 " This application will set the following channel variable upon completion:\n"
465 " VMSTATUS - This indicates the status of the execution of the VoiceMail\n"
466 " application. The possible values are:\n"
467 " SUCCESS | USEREXIT | FAILED\n\n"
469 " b - Play the 'busy' greeting to the calling party.\n"
470 " g(#) - Use the specified amount of gain when recording the voicemail\n"
471 " message. The units are whole-number decibels (dB).\n"
472 " s - Skip the playback of instructions for leaving a message to the\n"
474 " u - Play the 'unavailable greeting.\n";
476 static char *synopsis_vmain = "Check Voicemail messages";
478 static char *descrip_vmain =
479 " VoiceMailMain([mailbox][@context][|options]): This application allows the\n"
480 "calling party to check voicemail messages. A specific mailbox, and optional\n"
481 "corresponding context, may be specified. If a mailbox is not provided, the\n"
482 "calling party will be prompted to enter one. If a context is not specified,\n"
483 "the 'default' context will be used.\n\n"
485 " p - Consider the mailbox parameter as a prefix to the mailbox that\n"
486 " is entered by the caller.\n"
487 " g(#) - Use the specified amount of gain when recording a voicemail\n"
488 " message. The units are whole-number decibels (dB).\n"
489 " s - Skip checking the passcode for the mailbox.\n"
490 " a(#) - Skip folder prompt and go directly to folder specified.\n"
491 " Defaults to INBOX\n";
493 static char *synopsis_vm_box_exists =
494 "Check to see if Voicemail mailbox exists";
496 static char *descrip_vm_box_exists =
497 " MailboxExists(mailbox[@context][|options]): Check to see if the specified\n"
498 "mailbox exists. If no voicemail context is specified, the 'default' context\n"
500 " This application will set the following channel variable upon completion:\n"
501 " VMBOXEXISTSSTATUS - This will contain the status of the execution of the\n"
502 " MailboxExists application. Possible values include:\n"
503 " SUCCESS | FAILED\n\n"
504 " Options: (none)\n";
506 static char *synopsis_vmauthenticate = "Authenticate with Voicemail passwords";
508 static char *descrip_vmauthenticate =
509 " VMAuthenticate([mailbox][@context][|options]): This application behaves the\n"
510 "same way as the Authenticate application, but the passwords are taken from\n"
512 " If the mailbox is specified, only that mailbox's password will be considered\n"
513 "valid. If the mailbox is not specified, the channel variable AUTH_MAILBOX will\n"
514 "be set with the authenticated mailbox.\n\n"
516 " s - Skip playing the initial prompts.\n";
518 /* Leave a message */
519 static char *app = "VoiceMail";
521 /* Check mail, control, etc */
522 static char *app2 = "VoiceMailMain";
524 static char *app3 = "MailboxExists";
525 static char *app4 = "VMAuthenticate";
527 static AST_LIST_HEAD_STATIC(users, ast_vm_user);
528 static AST_LIST_HEAD_STATIC(zones, vm_zone);
529 static int maxsilence;
531 static int silencethreshold = 128;
532 static char serveremail[80];
533 static char mailcmd[160]; /* Configurable mail cmd */
534 static char externnotify[160];
535 static struct ast_smdi_interface *smdi_iface = NULL;
536 static char vmfmts[80];
537 static double volgain;
538 static int vmminsecs;
539 static int vmmaxsecs;
542 static int maxlogins;
544 /*! Poll mailboxes for changes since there is something external to
545 * app_voicemail that may change them. */
546 static unsigned int poll_mailboxes;
548 /*! Polling frequency */
549 static unsigned int poll_freq;
550 /*! By default, poll every 30 seconds */
551 #define DEFAULT_POLL_FREQ 30
553 AST_MUTEX_DEFINE_STATIC(poll_lock);
554 static ast_cond_t poll_cond = PTHREAD_COND_INITIALIZER;
555 static pthread_t poll_thread = AST_PTHREADT_NULL;
556 static unsigned char poll_thread_run;
558 /*! Subscription to ... MWI event subscriptions */
559 static struct ast_event_sub *mwi_sub_sub;
560 /*! Subscription to ... MWI event un-subscriptions */
561 static struct ast_event_sub *mwi_unsub_sub;
564 * \brief An MWI subscription
566 * This is so we can keep track of which mailboxes are subscribed to.
567 * This way, we know which mailboxes to poll when the pollmailboxes
568 * option is being used.
571 AST_RWLIST_ENTRY(mwi_sub) entry;
578 static AST_RWLIST_HEAD_STATIC(mwi_subs, mwi_sub);
580 /* custom password sounds */
581 static char vm_password[80] = "vm-password";
582 static char vm_newpassword[80] = "vm-newpassword";
583 static char vm_passchanged[80] = "vm-passchanged";
584 static char vm_reenterpassword[80] = "vm-reenterpassword";
585 static char vm_mismatch[80] = "vm-mismatch";
587 static struct ast_flags globalflags = {0};
589 static int saydurationminfo;
591 static char dialcontext[AST_MAX_CONTEXT];
592 static char callcontext[AST_MAX_CONTEXT];
593 static char exitcontext[AST_MAX_CONTEXT];
595 static char cidinternalcontexts[MAX_NUM_CID_CONTEXTS][64];
598 static char *emailbody = NULL;
599 static char *emailsubject = NULL;
600 static char *pagerbody = NULL;
601 static char *pagersubject = NULL;
602 static char fromstring[100];
603 static char pagerfromstring[100];
604 static char emailtitle[100];
605 static char charset[32] = "ISO-8859-1";
607 static unsigned char adsifdn[4] = "\x00\x00\x00\x0F";
608 static unsigned char adsisec[4] = "\x9B\xDB\xF7\xAC";
609 static int adsiver = 1;
610 static char emaildateformat[32] = "%A, %B %d, %Y at %r";
612 /* Forward declarations - generic */
613 static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu,int box);
614 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);
615 static int dialout(struct ast_channel *chan, struct ast_vm_user *vmu, char *num, char *outgoing_context);
616 static int play_record_review(struct ast_channel *chan, char *playfile, char *recordfile, int maxtime,
617 char *fmt, int outsidecaller, struct ast_vm_user *vmu, int *duration, const char *unlockdir,
618 signed char record_gain, struct vm_state *vms);
619 static int vm_tempgreeting(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, char *fmtc, signed char record_gain);
620 static int vm_play_folder_name(struct ast_channel *chan, char *mbox);
621 static int notify_new_message(struct ast_channel *chan, struct ast_vm_user *vmu, int msgnum, long duration, char *fmt, char *cidnum, char *cidname);
622 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);
623 static void apply_options(struct ast_vm_user *vmu, const char *options);
625 #if !(defined(ODBC_STORAGE) || defined(IMAP_STORAGE))
626 static int __has_voicemail(const char *context, const char *mailbox, const char *folder, int shortcircuit);
631 static void populate_defaults(struct ast_vm_user *vmu)
633 ast_copy_flags(vmu, (&globalflags), AST_FLAGS_ALL);
634 if (saydurationminfo)
635 vmu->saydurationm = saydurationminfo;
637 ast_copy_string(vmu->callback, callcontext, sizeof(vmu->callback));
639 ast_copy_string(vmu->dialout, dialcontext, sizeof(vmu->dialout));
641 ast_copy_string(vmu->exit, exitcontext, sizeof(vmu->exit));
643 vmu->maxsecs = vmmaxsecs;
645 vmu->maxmsg = maxmsg;
646 vmu->volgain = volgain;
649 static void apply_option(struct ast_vm_user *vmu, const char *var, const char *value)
652 if (!strcasecmp(var, "attach")) {
653 ast_set2_flag(vmu, ast_true(value), VM_ATTACH);
654 } else if (!strcasecmp(var, "attachfmt")) {
655 ast_copy_string(vmu->attachfmt, value, sizeof(vmu->attachfmt));
656 } else if (!strcasecmp(var, "serveremail")) {
657 ast_copy_string(vmu->serveremail, value, sizeof(vmu->serveremail));
658 } else if (!strcasecmp(var, "language")) {
659 ast_copy_string(vmu->language, value, sizeof(vmu->language));
660 } else if (!strcasecmp(var, "tz")) {
661 ast_copy_string(vmu->zonetag, value, sizeof(vmu->zonetag));
663 } else if (!strcasecmp(var, "imapuser")) {
664 ast_copy_string(vmu->imapuser, value, sizeof(vmu->imapuser));
665 } else if (!strcasecmp(var, "imappassword")) {
666 ast_copy_string(vmu->imappassword, value, sizeof(vmu->imappassword));
668 } else if (!strcasecmp(var, "delete") || !strcasecmp(var, "deletevoicemail")) {
669 ast_set2_flag(vmu, ast_true(value), VM_DELETE);
670 } else if (!strcasecmp(var, "saycid")){
671 ast_set2_flag(vmu, ast_true(value), VM_SAYCID);
672 } else if (!strcasecmp(var,"sendvoicemail")){
673 ast_set2_flag(vmu, ast_true(value), VM_SVMAIL);
674 } else if (!strcasecmp(var, "review")){
675 ast_set2_flag(vmu, ast_true(value), VM_REVIEW);
676 } else if (!strcasecmp(var, "tempgreetwarn")){
677 ast_set2_flag(vmu, ast_true(value), VM_TEMPGREETWARN);
678 } else if (!strcasecmp(var, "operator")){
679 ast_set2_flag(vmu, ast_true(value), VM_OPERATOR);
680 } else if (!strcasecmp(var, "envelope")){
681 ast_set2_flag(vmu, ast_true(value), VM_ENVELOPE);
682 } else if (!strcasecmp(var, "sayduration")){
683 ast_set2_flag(vmu, ast_true(value), VM_SAYDURATION);
684 } else if (!strcasecmp(var, "saydurationm")){
685 if (sscanf(value, "%d", &x) == 1) {
686 vmu->saydurationm = x;
688 ast_log(LOG_WARNING, "Invalid min duration for say duration\n");
690 } else if (!strcasecmp(var, "forcename")){
691 ast_set2_flag(vmu, ast_true(value), VM_FORCENAME);
692 } else if (!strcasecmp(var, "forcegreetings")){
693 ast_set2_flag(vmu, ast_true(value), VM_FORCEGREET);
694 } else if (!strcasecmp(var, "callback")) {
695 ast_copy_string(vmu->callback, value, sizeof(vmu->callback));
696 } else if (!strcasecmp(var, "dialout")) {
697 ast_copy_string(vmu->dialout, value, sizeof(vmu->dialout));
698 } else if (!strcasecmp(var, "exitcontext")) {
699 ast_copy_string(vmu->exit, value, sizeof(vmu->exit));
700 } else if (!strcasecmp(var, "maxmessage")) {
701 if (vmu->maxsecs <= 0) {
702 ast_log(LOG_WARNING, "Invalid max message length of %s. Using global value %i\n", value, vmmaxsecs);
703 vmu->maxsecs = vmmaxsecs;
705 vmu->maxsecs = atoi(value);
707 } else if (!strcasecmp(var, "maxmsg")) {
708 vmu->maxmsg = atoi(value);
709 if (vmu->maxmsg <= 0) {
710 ast_log(LOG_WARNING, "Invalid number of messages per folder maxmsg=%s. Using default value %i\n", value, MAXMSG);
711 vmu->maxmsg = MAXMSG;
712 } else if (vmu->maxmsg > MAXMSGLIMIT) {
713 ast_log(LOG_WARNING, "Maximum number of messages per folder is %i. Cannot accept value maxmsg=%s\n", MAXMSGLIMIT, value);
714 vmu->maxmsg = MAXMSGLIMIT;
716 } else if (!strcasecmp(var, "volgain")) {
717 sscanf(value, "%lf", &vmu->volgain);
718 } else if (!strcasecmp(var, "options")) {
719 apply_options(vmu, value);
723 static int change_password_realtime(struct ast_vm_user *vmu, const char *password)
726 if (!ast_strlen_zero(vmu->uniqueid)) {
727 res = ast_update_realtime("voicemail", "uniqueid", vmu->uniqueid, "password", password, NULL);
729 ast_copy_string(vmu->password, password, sizeof(vmu->password));
739 static void apply_options(struct ast_vm_user *vmu, const char *options)
740 { /* Destructively Parse options and apply */
744 stringp = ast_strdupa(options);
745 while ((s = strsep(&stringp, "|"))) {
747 if ((var = strsep(&value, "=")) && value) {
748 apply_option(vmu, var, value);
753 static void apply_options_full(struct ast_vm_user *retval, struct ast_variable *var)
755 struct ast_variable *tmp;
758 if (!strcasecmp(tmp->name, "vmsecret")) {
759 ast_copy_string(retval->password, tmp->value, sizeof(retval->password));
760 } else if (!strcasecmp(tmp->name, "secret") || !strcasecmp(tmp->name, "password")) { /* don't overwrite vmsecret if it exists */
761 if (ast_strlen_zero(retval->password))
762 ast_copy_string(retval->password, tmp->value, sizeof(retval->password));
763 } else if (!strcasecmp(tmp->name, "uniqueid")) {
764 ast_copy_string(retval->uniqueid, tmp->value, sizeof(retval->uniqueid));
765 } else if (!strcasecmp(tmp->name, "pager")) {
766 ast_copy_string(retval->pager, tmp->value, sizeof(retval->pager));
767 } else if (!strcasecmp(tmp->name, "email")) {
768 ast_copy_string(retval->email, tmp->value, sizeof(retval->email));
769 } else if (!strcasecmp(tmp->name, "fullname")) {
770 ast_copy_string(retval->fullname, tmp->value, sizeof(retval->fullname));
771 } else if (!strcasecmp(tmp->name, "context")) {
772 ast_copy_string(retval->context, tmp->value, sizeof(retval->context));
774 } else if (!strcasecmp(tmp->name, "imapuser")) {
775 ast_copy_string(retval->imapuser, tmp->value, sizeof(retval->imapuser));
776 } else if (!strcasecmp(tmp->name, "imappassword")) {
777 ast_copy_string(retval->imappassword, tmp->value, sizeof(retval->imappassword));
780 apply_option(retval, tmp->name, tmp->value);
785 static struct ast_vm_user *find_user_realtime(struct ast_vm_user *ivm, const char *context, const char *mailbox)
787 struct ast_variable *var;
788 struct ast_vm_user *retval;
790 if ((retval = (ivm ? ivm : ast_calloc(1, sizeof(*retval))))) {
792 ast_set_flag(retval, VM_ALLOCED);
794 memset(retval, 0, sizeof(*retval));
796 ast_copy_string(retval->mailbox, mailbox, sizeof(retval->mailbox));
797 populate_defaults(retval);
798 if (!context && ast_test_flag((&globalflags), VM_SEARCH))
799 var = ast_load_realtime("voicemail", "mailbox", mailbox, NULL);
801 var = ast_load_realtime("voicemail", "mailbox", mailbox, "context", context, NULL);
803 apply_options_full(retval, var);
804 ast_variables_destroy(var);
814 static struct ast_vm_user *find_user(struct ast_vm_user *ivm, const char *context, const char *mailbox)
816 /* This function could be made to generate one from a database, too */
817 struct ast_vm_user *vmu=NULL, *cur;
818 AST_LIST_LOCK(&users);
820 if (!context && !ast_test_flag((&globalflags), VM_SEARCH))
823 AST_LIST_TRAVERSE(&users, cur, list) {
824 if (ast_test_flag((&globalflags), VM_SEARCH) && !strcasecmp(mailbox, cur->mailbox))
826 if (context && (!strcasecmp(context, cur->context)) && (!strcasecmp(mailbox, cur->mailbox)))
830 /* Make a copy, so that on a reload, we have no race */
831 if ((vmu = (ivm ? ivm : ast_malloc(sizeof(*vmu))))) {
832 memcpy(vmu, cur, sizeof(*vmu));
833 ast_set2_flag(vmu, !ivm, VM_ALLOCED);
834 AST_LIST_NEXT(vmu, list) = NULL;
837 vmu = find_user_realtime(ivm, context, mailbox);
838 AST_LIST_UNLOCK(&users);
842 static int reset_user_pw(const char *context, const char *mailbox, const char *newpass)
844 /* This function could be made to generate one from a database, too */
845 struct ast_vm_user *cur;
847 AST_LIST_LOCK(&users);
848 AST_LIST_TRAVERSE(&users, cur, list) {
849 if ((!context || !strcasecmp(context, cur->context)) &&
850 (!strcasecmp(mailbox, cur->mailbox)))
854 ast_copy_string(cur->password, newpass, sizeof(cur->password));
857 AST_LIST_UNLOCK(&users);
861 static void vm_change_password(struct ast_vm_user *vmu, const char *newpassword)
863 struct ast_config *cfg=NULL;
864 struct ast_variable *var=NULL;
865 struct ast_category *cat=NULL;
866 char *category=NULL, *value=NULL, *new=NULL;
867 const char *tmp=NULL;
869 if (!change_password_realtime(vmu, newpassword))
872 /* check voicemail.conf */
873 if ((cfg = ast_config_load_with_comments(VOICEMAIL_CONFIG))) {
874 while ((category = ast_category_browse(cfg, category))) {
875 if (!strcasecmp(category, vmu->context)) {
876 if (!(tmp = ast_variable_retrieve(cfg, category, vmu->mailbox))) {
877 ast_log(LOG_WARNING, "We could not find the mailbox.\n");
880 value = strstr(tmp,",");
882 ast_log(LOG_WARNING, "variable has bad format.\n");
885 new = alloca((strlen(value)+strlen(newpassword)+1));
886 sprintf(new,"%s%s", newpassword, value);
887 if (!(cat = ast_category_get(cfg, category))) {
888 ast_log(LOG_WARNING, "Failed to get category structure.\n");
891 ast_variable_update(cat, vmu->mailbox, new, NULL, 0);
894 /* save the results */
895 reset_user_pw(vmu->context, vmu->mailbox, newpassword);
896 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
897 config_text_file_save(VOICEMAIL_CONFIG, cfg, "AppVoicemail");
901 /* check users.conf and update the password stored for the mailbox*/
902 /* if no vmsecret entry exists create one. */
903 if ((cfg = ast_config_load_with_comments("users.conf"))) {
904 ast_debug(4, "we are looking for %s\n", vmu->mailbox);
905 while ((category = ast_category_browse(cfg, category))) {
906 ast_debug(4, "users.conf: %s\n", category);
907 if (!strcasecmp(category, vmu->mailbox)) {
908 if (!(tmp = ast_variable_retrieve(cfg, category, "vmsecret"))) {
909 ast_debug(3, "looks like we need to make vmsecret!\n");
910 var = ast_variable_new("vmsecret", newpassword);
912 new = alloca(strlen(newpassword)+1);
913 sprintf(new, "%s", newpassword);
914 if (!(cat = ast_category_get(cfg, category))) {
915 ast_debug(4, "failed to get category!\n");
919 ast_variable_update(cat, "vmsecret", new, NULL, 0);
921 ast_variable_append(cat, var);
924 /* save the results and clean things up */
925 reset_user_pw(vmu->context, vmu->mailbox, newpassword);
926 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
927 config_text_file_save("users.conf", cfg, "AppVoicemail");
931 static void vm_change_password_shell(struct ast_vm_user *vmu, char *newpassword)
934 snprintf(buf,255,"%s %s %s %s",ext_pass_cmd,vmu->context,vmu->mailbox,newpassword);
935 if (!ast_safe_system(buf))
936 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
939 static int make_dir(char *dest, int len, const char *context, const char *ext, const char *folder)
941 return snprintf(dest, len, "%s%s/%s/%s", VM_SPOOL_DIR, context, ext, folder);
945 static int make_gsm_file(char *dest, char *imapuser, char *dir, int num)
948 if ((res = ast_mkdir(dir, 01777))) {
949 ast_log(LOG_WARNING, "ast_mkdir '%s' failed: %s\n", dir, strerror(res));
950 return sprintf(dest, "%s/msg%04d", dir, num);
952 /* return sprintf(dest, "%s/s/msg%04d", dir, imapuser, num); */
953 return sprintf(dest, "%s/msg%04d", dir, num);
956 static void vm_imap_delete(int msgnum, struct vm_state *vms)
958 unsigned long messageNum = 0;
961 /* find real message number based on msgnum */
962 /* this may be an index into vms->msgArray based on the msgnum. */
964 messageNum = vms->msgArray[msgnum];
965 if (messageNum == 0) {
966 ast_log(LOG_WARNING, "msgnum %d, mailbox message %lu is zero.\n",msgnum,messageNum);
969 ast_debug(3, "deleting msgnum %d, which is mailbox message %lu\n",msgnum,messageNum);
971 sprintf (arg,"%lu",messageNum);
972 mail_setflag (vms->mailstream,arg,"\\DELETED");
976 static int make_file(char *dest, int len, char *dir, int num)
978 return snprintf(dest, len, "%s/msg%04d", dir, num);
981 /*! \brief basically mkdir -p $dest/$context/$ext/$folder
982 * \param dest String. base directory.
983 * \param len Length of dest.
984 * \param context String. Ignored if is null or empty string.
985 * \param ext String. Ignored if is null or empty string.
986 * \param folder String. Ignored if is null or empty string.
987 * \return -1 on failure, 0 on success.
989 static int create_dirpath(char *dest, int len, const char *context, const char *ext, const char *folder)
991 mode_t mode = VOICEMAIL_DIR_MODE;
994 make_dir(dest, len, context, ext, folder);
995 if ((res = ast_mkdir(dest, mode))) {
996 ast_log(LOG_WARNING, "ast_mkdir '%s' failed: %s\n", dest, strerror(res));
1002 /*! \brief Lock file path
1003 only return failure if ast_lock_path returns 'timeout',
1004 not if the path does not exist or any other reason
1006 static int vm_lock_path(const char *path)
1008 switch (ast_lock_path(path)) {
1009 case AST_LOCK_TIMEOUT:
1018 static int retrieve_file(char *dir, int msgnum)
1024 void *fdm = MAP_FAILED;
1025 SQLSMALLINT colcount=0;
1032 SQLSMALLINT datatype;
1033 SQLSMALLINT decimaldigits;
1034 SQLSMALLINT nullable;
1040 char full_fn[PATH_MAX];
1043 struct odbc_obj *obj;
1044 obj = ast_odbc_request_obj(odbc_database, 0);
1046 ast_copy_string(fmt, vmfmts, sizeof(fmt));
1047 c = strchr(fmt, '|');
1050 if (!strcasecmp(fmt, "wav49"))
1052 snprintf(msgnums, sizeof(msgnums),"%d", msgnum);
1054 make_file(fn, sizeof(fn), dir, msgnum);
1056 ast_copy_string(fn, dir, sizeof(fn));
1057 snprintf(full_fn, sizeof(full_fn), "%s.txt", fn);
1059 if (!(f = fopen(full_fn, "w+"))) {
1060 ast_log(LOG_WARNING, "Failed to open/create '%s'\n", full_fn);
1064 snprintf(full_fn, sizeof(full_fn), "%s.%s", fn, fmt);
1065 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
1066 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1067 ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
1068 ast_odbc_release_obj(obj);
1071 snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE dir=? AND msgnum=?",odbc_table);
1072 res = SQLPrepare(stmt, (unsigned char *)sql, SQL_NTS);
1073 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1074 ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql);
1075 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1076 ast_odbc_release_obj(obj);
1079 SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(dir), 0, (void *)dir, 0, NULL);
1080 SQLBindParameter(stmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(msgnums), 0, (void *)msgnums, 0, NULL);
1081 res = ast_odbc_smart_execute(obj, stmt);
1082 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1083 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
1084 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1085 ast_odbc_release_obj(obj);
1088 res = SQLFetch(stmt);
1089 if (res == SQL_NO_DATA) {
1090 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1091 ast_odbc_release_obj(obj);
1094 else if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1095 ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
1096 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1097 ast_odbc_release_obj(obj);
1100 fd = open(full_fn, O_RDWR | O_CREAT | O_TRUNC, VOICEMAIL_FILE_MODE);
1102 ast_log(LOG_WARNING, "Failed to write '%s': %s\n", full_fn, strerror(errno));
1103 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1104 ast_odbc_release_obj(obj);
1107 res = SQLNumResultCols(stmt, &colcount);
1108 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1109 ast_log(LOG_WARNING, "SQL Column Count error!\n[%s]\n\n", sql);
1110 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1111 ast_odbc_release_obj(obj);
1115 fprintf(f, "[message]\n");
1116 for (x=0;x<colcount;x++) {
1118 collen = sizeof(coltitle);
1119 res = SQLDescribeCol(stmt, x + 1, (unsigned char *)coltitle, sizeof(coltitle), &collen,
1120 &datatype, &colsize, &decimaldigits, &nullable);
1121 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1122 ast_log(LOG_WARNING, "SQL Describe Column error!\n[%s]\n\n", sql);
1123 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1124 ast_odbc_release_obj(obj);
1127 if (!strcasecmp(coltitle, "recording")) {
1129 res = SQLGetData(stmt, x + 1, SQL_BINARY, rowdata, 0, &colsize2);
1133 lseek(fd, fdlen - 1, SEEK_SET);
1134 if (write(fd, tmp, 1) != 1) {
1139 /* Read out in small chunks */
1140 for (offset = 0; offset < colsize2; offset += CHUNKSIZE) {
1141 if ((fdm = mmap(NULL, CHUNKSIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, offset)) == MAP_FAILED) {
1142 ast_log(LOG_WARNING, "Could not mmap the output file: %s (%d)\n", strerror(errno), errno);
1143 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
1144 ast_odbc_release_obj(obj);
1147 res = SQLGetData(stmt, x + 1, SQL_BINARY, fdm, CHUNKSIZE, NULL);
1148 munmap(fdm, CHUNKSIZE);
1149 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1150 ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
1152 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
1153 ast_odbc_release_obj(obj);
1158 truncate(full_fn, fdlen);
1161 res = SQLGetData(stmt, x + 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
1162 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1163 ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
1164 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1165 ast_odbc_release_obj(obj);
1168 if (strcasecmp(coltitle, "msgnum") && strcasecmp(coltitle, "dir") && f)
1169 fprintf(f, "%s=%s\n", coltitle, rowdata);
1172 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1173 ast_odbc_release_obj(obj);
1175 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
1184 static int remove_file(char *dir, int msgnum)
1187 char full_fn[PATH_MAX];
1191 snprintf(msgnums, sizeof(msgnums), "%d", msgnum);
1192 make_file(fn, sizeof(fn), dir, msgnum);
1194 ast_copy_string(fn, dir, sizeof(fn));
1195 ast_filedelete(fn, NULL);
1196 if (ast_check_realtime("voicemail_data")) {
1197 ast_destroy_realtime("voicemail_data", "filename", fn, NULL);
1199 snprintf(full_fn, sizeof(full_fn), "%s.txt", fn);
1204 static int last_message_index(struct ast_vm_user *vmu, char *dir)
1212 struct odbc_obj *obj;
1213 obj = ast_odbc_request_obj(odbc_database, 0);
1215 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
1216 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1217 ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
1218 ast_odbc_release_obj(obj);
1221 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir=?",odbc_table);
1222 res = SQLPrepare(stmt, (unsigned char *)sql, SQL_NTS);
1223 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1224 ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql);
1225 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1226 ast_odbc_release_obj(obj);
1229 SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(dir), 0, (void *)dir, 0, NULL);
1230 res = ast_odbc_smart_execute(obj, stmt);
1231 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1232 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
1233 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1234 ast_odbc_release_obj(obj);
1237 res = SQLFetch(stmt);
1238 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1239 ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
1240 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1241 ast_odbc_release_obj(obj);
1244 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
1245 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1246 ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
1247 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1248 ast_odbc_release_obj(obj);
1251 if (sscanf(rowdata, "%d", &x) != 1)
1252 ast_log(LOG_WARNING, "Failed to read message count!\n");
1253 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1254 ast_odbc_release_obj(obj);
1256 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
1261 static int message_exists(char *dir, int msgnum)
1270 struct odbc_obj *obj;
1271 obj = ast_odbc_request_obj(odbc_database, 0);
1273 snprintf(msgnums, sizeof(msgnums), "%d", msgnum);
1274 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
1275 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1276 ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
1277 ast_odbc_release_obj(obj);
1280 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir=? AND msgnum=?",odbc_table);
1281 res = SQLPrepare(stmt, (unsigned char *)sql, SQL_NTS);
1282 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1283 ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql);
1284 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1285 ast_odbc_release_obj(obj);
1288 SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(dir), 0, (void *)dir, 0, NULL);
1289 SQLBindParameter(stmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(msgnums), 0, (void *)msgnums, 0, NULL);
1290 res = ast_odbc_smart_execute(obj, stmt);
1291 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1292 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
1293 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1294 ast_odbc_release_obj(obj);
1297 res = SQLFetch(stmt);
1298 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1299 ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
1300 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1301 ast_odbc_release_obj(obj);
1304 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
1305 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1306 ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
1307 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1308 ast_odbc_release_obj(obj);
1311 if (sscanf(rowdata, "%d", &x) != 1)
1312 ast_log(LOG_WARNING, "Failed to read message count!\n");
1313 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1314 ast_odbc_release_obj(obj);
1316 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
1321 static int count_messages(struct ast_vm_user *vmu, char *dir)
1323 return last_message_index(vmu, dir) + 1;
1326 static void delete_file(char *sdir, int smsg)
1333 struct odbc_obj *obj;
1334 obj = ast_odbc_request_obj(odbc_database, 0);
1336 snprintf(msgnums, sizeof(msgnums), "%d", smsg);
1337 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
1338 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1339 ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
1340 ast_odbc_release_obj(obj);
1343 snprintf(sql, sizeof(sql), "DELETE FROM %s WHERE dir=? AND msgnum=?",odbc_table);
1344 res = SQLPrepare(stmt, (unsigned char *)sql, SQL_NTS);
1345 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1346 ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql);
1347 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1348 ast_odbc_release_obj(obj);
1351 SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(sdir), 0, (void *)sdir, 0, NULL);
1352 SQLBindParameter(stmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(msgnums), 0, (void *)msgnums, 0, NULL);
1353 res = ast_odbc_smart_execute(obj, stmt);
1354 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1355 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
1356 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1357 ast_odbc_release_obj(obj);
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 void copy_file(char *sdir, int smsg, char *ddir, int dmsg, char *dmailboxuser, char *dmailboxcontext)
1375 struct odbc_obj *obj;
1377 delete_file(ddir, dmsg);
1378 obj = ast_odbc_request_obj(odbc_database, 0);
1380 snprintf(msgnums, sizeof(msgnums), "%d", smsg);
1381 snprintf(msgnumd, sizeof(msgnumd), "%d", dmsg);
1382 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
1383 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1384 ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
1385 ast_odbc_release_obj(obj);
1388 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);
1389 res = SQLPrepare(stmt, (unsigned char *)sql, SQL_NTS);
1390 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1391 ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql);
1392 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1393 ast_odbc_release_obj(obj);
1396 SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(ddir), 0, (void *)ddir, 0, NULL);
1397 SQLBindParameter(stmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(msgnumd), 0, (void *)msgnumd, 0, NULL);
1398 SQLBindParameter(stmt, 3, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(dmailboxuser), 0, (void *)dmailboxuser, 0, NULL);
1399 SQLBindParameter(stmt, 4, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(dmailboxcontext), 0, (void *)dmailboxcontext, 0, NULL);
1400 SQLBindParameter(stmt, 5, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(sdir), 0, (void *)sdir, 0, NULL);
1401 SQLBindParameter(stmt, 6, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(msgnums), 0, (void *)msgnums, 0, NULL);
1402 res = ast_odbc_smart_execute(obj, stmt);
1403 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1404 ast_log(LOG_WARNING, "SQL Execute error!\n[%s] (You probably don't have MySQL 4.1 or later installed)\n\n", sql);
1405 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1406 ast_odbc_release_obj(obj);
1409 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1410 ast_odbc_release_obj(obj);
1412 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
1417 static int store_file(char *dir, char *mailboxuser, char *mailboxcontext, int msgnum)
1422 void *fdm = MAP_FAILED;
1429 char full_fn[PATH_MAX];
1432 const char *context="", *macrocontext="", *callerid="", *origtime="", *duration="";
1433 const char *category = "";
1434 struct ast_config *cfg=NULL;
1435 struct odbc_obj *obj;
1437 delete_file(dir, msgnum);
1438 obj = ast_odbc_request_obj(odbc_database, 0);
1440 ast_copy_string(fmt, vmfmts, sizeof(fmt));
1441 c = strchr(fmt, '|');
1444 if (!strcasecmp(fmt, "wav49"))
1446 snprintf(msgnums, sizeof(msgnums),"%d", msgnum);
1448 make_file(fn, sizeof(fn), dir, msgnum);
1450 ast_copy_string(fn, dir, sizeof(fn));
1451 snprintf(full_fn, sizeof(full_fn), "%s.txt", fn);
1452 cfg = ast_config_load(full_fn);
1453 snprintf(full_fn, sizeof(full_fn), "%s.%s", fn, fmt);
1454 fd = open(full_fn, O_RDWR);
1456 ast_log(LOG_WARNING, "Open of sound file '%s' failed: %s\n", full_fn, strerror(errno));
1457 ast_odbc_release_obj(obj);
1461 context = ast_variable_retrieve(cfg, "message", "context");
1462 if (!context) context = "";
1463 macrocontext = ast_variable_retrieve(cfg, "message", "macrocontext");
1464 if (!macrocontext) macrocontext = "";
1465 callerid = ast_variable_retrieve(cfg, "message", "callerid");
1466 if (!callerid) callerid = "";
1467 origtime = ast_variable_retrieve(cfg, "message", "origtime");
1468 if (!origtime) origtime = "";
1469 duration = ast_variable_retrieve(cfg, "message", "duration");
1470 if (!duration) duration = "";
1471 category = ast_variable_retrieve(cfg, "message", "category");
1472 if (!category) category = "";
1474 fdlen = lseek(fd, 0, SEEK_END);
1475 lseek(fd, 0, SEEK_SET);
1476 printf("Length is %zd\n", fdlen);
1477 fdm = mmap(NULL, fdlen, PROT_READ | PROT_WRITE, MAP_SHARED,fd, 0);
1478 if (fdm == MAP_FAILED) {
1479 ast_log(LOG_WARNING, "Memory map failed!\n");
1480 ast_odbc_release_obj(obj);
1483 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
1484 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1485 ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
1486 ast_odbc_release_obj(obj);
1489 if (!ast_strlen_zero(category))
1490 snprintf(sql, sizeof(sql), "INSERT INTO %s (dir,msgnum,recording,context,macrocontext,callerid,origtime,duration,mailboxuser,mailboxcontext,category) VALUES (?,?,?,?,?,?,?,?,?,?,?)",odbc_table);
1492 snprintf(sql, sizeof(sql), "INSERT INTO %s (dir,msgnum,recording,context,macrocontext,callerid,origtime,duration,mailboxuser,mailboxcontext) VALUES (?,?,?,?,?,?,?,?,?,?)",odbc_table);
1493 res = SQLPrepare(stmt, (unsigned char *)sql, SQL_NTS);
1494 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1495 ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql);
1496 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1497 ast_odbc_release_obj(obj);
1500 len = fdlen; /* SQL_LEN_DATA_AT_EXEC(fdlen); */
1501 SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(dir), 0, (void *)dir, 0, NULL);
1502 SQLBindParameter(stmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(msgnums), 0, (void *)msgnums, 0, NULL);
1503 SQLBindParameter(stmt, 3, SQL_PARAM_INPUT, SQL_C_BINARY, SQL_LONGVARBINARY, fdlen, 0, (void *)fdm, fdlen, &len);
1504 SQLBindParameter(stmt, 4, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(context), 0, (void *)context, 0, NULL);
1505 SQLBindParameter(stmt, 5, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(macrocontext), 0, (void *)macrocontext, 0, NULL);
1506 SQLBindParameter(stmt, 6, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(callerid), 0, (void *)callerid, 0, NULL);
1507 SQLBindParameter(stmt, 7, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(origtime), 0, (void *)origtime, 0, NULL);
1508 SQLBindParameter(stmt, 8, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(duration), 0, (void *)duration, 0, NULL);
1509 SQLBindParameter(stmt, 9, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(mailboxuser), 0, (void *)mailboxuser, 0, NULL);
1510 SQLBindParameter(stmt, 10, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(mailboxcontext), 0, (void *)mailboxcontext, 0, NULL);
1511 if (!ast_strlen_zero(category))
1512 SQLBindParameter(stmt, 11, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(category), 0, (void *)category, 0, NULL);
1513 res = ast_odbc_smart_execute(obj, stmt);
1514 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1515 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
1516 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1517 ast_odbc_release_obj(obj);
1520 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1521 ast_odbc_release_obj(obj);
1523 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
1526 ast_config_destroy(cfg);
1527 if (fdm != MAP_FAILED)
1534 static void rename_file(char *sdir, int smsg, char *mailboxuser, char *mailboxcontext, char *ddir, int dmsg)
1541 struct odbc_obj *obj;
1543 delete_file(ddir, dmsg);
1544 obj = ast_odbc_request_obj(odbc_database, 0);
1546 snprintf(msgnums, sizeof(msgnums), "%d", smsg);
1547 snprintf(msgnumd, sizeof(msgnumd), "%d", dmsg);
1548 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
1549 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1550 ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
1551 ast_odbc_release_obj(obj);
1554 snprintf(sql, sizeof(sql), "UPDATE %s SET dir=?, msgnum=?, mailboxuser=?, mailboxcontext=? WHERE dir=? AND msgnum=?",odbc_table);
1555 res = SQLPrepare(stmt, (unsigned char *)sql, SQL_NTS);
1556 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1557 ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql);
1558 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1559 ast_odbc_release_obj(obj);
1562 SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(ddir), 0, (void *)ddir, 0, NULL);
1563 SQLBindParameter(stmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(msgnumd), 0, (void *)msgnumd, 0, NULL);
1564 SQLBindParameter(stmt, 3, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(mailboxuser), 0, (void *)mailboxuser, 0, NULL);
1565 SQLBindParameter(stmt, 4, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(mailboxcontext), 0, (void *)mailboxcontext, 0, NULL);
1566 SQLBindParameter(stmt, 5, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(sdir), 0, (void *)sdir, 0, NULL);
1567 SQLBindParameter(stmt, 6, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(msgnums), 0, (void *)msgnums, 0, NULL);
1568 res = ast_odbc_smart_execute(obj, stmt);
1569 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1570 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
1571 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1572 ast_odbc_release_obj(obj);
1575 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1576 ast_odbc_release_obj(obj);
1578 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
1584 #ifndef IMAP_STORAGE
1585 static int count_messages(struct ast_vm_user *vmu, char *dir)
1587 /* Find all .txt files - even if they are not in sequence from 0000 */
1591 struct dirent *vment = NULL;
1593 if (vm_lock_path(dir))
1594 return ERROR_LOCK_PATH;
1596 if ((vmdir = opendir(dir))) {
1597 while ((vment = readdir(vmdir))) {
1598 if (strlen(vment->d_name) > 7 && !strncmp(vment->d_name + 7, ".txt", 4))
1603 ast_unlock_path(dir);
1608 static void rename_file(char *sfn, char *dfn)
1610 char stxt[PATH_MAX];
1611 char dtxt[PATH_MAX];
1612 ast_filerename(sfn,dfn,NULL);
1613 snprintf(stxt, sizeof(stxt), "%s.txt", sfn);
1614 snprintf(dtxt, sizeof(dtxt), "%s.txt", dfn);
1615 if (ast_check_realtime("voicemail_data")) {
1616 ast_update_realtime("voicemail_data", "filename", sfn, "filename", dfn, NULL);
1621 static int copy(char *infile, char *outfile)
1629 #ifdef HARDLINK_WHEN_POSSIBLE
1630 /* Hard link if possible; saves disk space & is faster */
1631 if (link(infile, outfile)) {
1633 if ((ifd = open(infile, O_RDONLY)) < 0) {
1634 ast_log(LOG_WARNING, "Unable to open %s in read-only mode\n", infile);
1637 if ((ofd = open(outfile, O_WRONLY | O_TRUNC | O_CREAT, VOICEMAIL_FILE_MODE)) < 0) {
1638 ast_log(LOG_WARNING, "Unable to open %s in write-only mode\n", outfile);
1643 len = read(ifd, buf, sizeof(buf));
1645 ast_log(LOG_WARNING, "Read failed on %s: %s\n", infile, strerror(errno));
1651 res = write(ofd, buf, len);
1652 if (errno == ENOMEM || errno == ENOSPC || res != len) {
1653 ast_log(LOG_WARNING, "Write failed on %s (%d of %d): %s\n", outfile, res, len, strerror(errno));
1663 #ifdef HARDLINK_WHEN_POSSIBLE
1665 /* Hard link succeeded */
1671 static void copy_file(char *frompath, char *topath)
1673 char frompath2[PATH_MAX], topath2[PATH_MAX];
1674 struct ast_variable *tmp,*var = NULL;
1675 char *origmailbox = NULL, *context = NULL, *macrocontext = NULL, *exten = NULL, *priority = NULL, *callerchan = NULL, *callerid = NULL, *origdate = NULL, *origtime = NULL, *category = NULL, *duration = NULL;
1676 ast_filecopy(frompath, topath, NULL);
1677 snprintf(frompath2, sizeof(frompath2), "%s.txt", frompath);
1678 snprintf(topath2, sizeof(topath2), "%s.txt", topath);
1679 if (ast_check_realtime("voicemail_data")) {
1680 var = ast_load_realtime("voicemail_data", "filename", frompath, NULL);
1681 /* This cycle converts ast_variable linked list, to va_list list of arguments, may be there is a better way to do it? */
1682 for (tmp = var; tmp; tmp = tmp->next) {
1683 if (!strcasecmp(tmp->name, "origmailbox")) {
1684 origmailbox = tmp->value;
1685 } else if (!strcasecmp(tmp->name, "context")) {
1686 context = tmp->value;
1687 } else if (!strcasecmp(tmp->name, "macrocontext")) {
1688 macrocontext = tmp->value;
1689 } else if (!strcasecmp(tmp->name, "exten")) {
1691 } else if (!strcasecmp(tmp->name, "priority")) {
1692 priority = tmp->value;
1693 } else if (!strcasecmp(tmp->name, "callerchan")) {
1694 callerchan = tmp->value;
1695 } else if (!strcasecmp(tmp->name, "callerid")) {
1696 callerid = tmp->value;
1697 } else if (!strcasecmp(tmp->name, "origdate")) {
1698 origdate = tmp->value;
1699 } else if (!strcasecmp(tmp->name, "origtime")) {
1700 origtime = tmp->value;
1701 } else if (!strcasecmp(tmp->name, "category")) {
1702 category = tmp->value;
1703 } else if (!strcasecmp(tmp->name, "duration")) {
1704 duration = tmp->value;
1707 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);
1709 copy(frompath2, topath2);
1710 ast_variables_destroy(var);
1715 * A negative return value indicates an error.
1716 * \note Should always be called with a lock already set on dir.
1718 static int last_message_index(struct ast_vm_user *vmu, char *dir)
1721 unsigned char map[MAXMSGLIMIT] = "";
1723 struct dirent *msgdirent;
1726 /* Reading the entire directory into a file map scales better than
1727 * doing a stat repeatedly on a predicted sequence. I suspect this
1728 * is partially due to stat(2) internally doing a readdir(2) itself to
1729 * find each file. */
1730 msgdir = opendir(dir);
1731 while ((msgdirent = readdir(msgdir))) {
1732 if (sscanf(msgdirent->d_name, "msg%d", &msgdirint) == 1 && msgdirint < MAXMSGLIMIT)
1737 for (x = 0; x < vmu->maxmsg; x++) {
1747 static int vm_delete(char *file)
1752 txtsize = (strlen(file) + 5)*sizeof(char);
1753 txt = alloca(txtsize);
1754 /* Sprintf here would safe because we alloca'd exactly the right length,
1755 * but trying to eliminate all sprintf's anyhow
1757 if (ast_check_realtime("voicemail_data")) {
1758 ast_destroy_realtime("voicemail_data", "filename", file, NULL);
1760 snprintf(txt, txtsize, "%s.txt", file);
1762 return ast_filedelete(file, NULL);
1765 static int inbuf(struct baseio *bio, FILE *fi)
1772 if ((l = fread(bio->iobuf,1,BASEMAXINLINE,fi)) <= 0) {
1786 static int inchar(struct baseio *bio, FILE *fi)
1788 if (bio->iocp>=bio->iolen) {
1789 if (!inbuf(bio, fi))
1793 return bio->iobuf[bio->iocp++];
1796 static int ochar(struct baseio *bio, int c, FILE *so)
1798 if (bio->linelength >= BASELINELEN) {
1799 if (fputs(eol,so) == EOF)
1805 if (putc(((unsigned char)c),so) == EOF)
1813 static int base_encode(char *filename, FILE *so)
1815 unsigned char dtable[BASEMAXINLINE];
1820 memset(&bio, 0, sizeof(bio));
1821 bio.iocp = BASEMAXINLINE;
1823 if (!(fi = fopen(filename, "rb"))) {
1824 ast_log(LOG_WARNING, "Failed to open log file: %s: %s\n", filename, strerror(errno));
1828 for (i= 0; i<9; i++) {
1831 dtable[26+i]= 'a'+i;
1832 dtable[26+i+9]= 'j'+i;
1834 for (i= 0; i<8; i++) {
1835 dtable[i+18]= 'S'+i;
1836 dtable[26+i+18]= 's'+i;
1838 for (i= 0; i<10; i++) {
1839 dtable[52+i]= '0'+i;
1845 unsigned char igroup[3], ogroup[4];
1848 igroup[0]= igroup[1]= igroup[2]= 0;
1850 for (n= 0;n<3;n++) {
1851 if ((c = inchar(&bio, fi)) == EOF) {
1856 igroup[n]= (unsigned char)c;
1860 ogroup[0]= dtable[igroup[0]>>2];
1861 ogroup[1]= dtable[((igroup[0]&3)<<4) | (igroup[1]>>4)];
1862 ogroup[2]= dtable[((igroup[1]&0xF)<<2) | (igroup[2]>>6)];
1863 ogroup[3]= dtable[igroup[2]&0x3F];
1873 ochar(&bio, ogroup[i], so);
1877 if (fputs(eol,so) == EOF)
1885 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)
1888 /* Prepare variables for substitution in email body and subject */
1889 pbx_builtin_setvar_helper(ast, "VM_NAME", vmu->fullname);
1890 pbx_builtin_setvar_helper(ast, "VM_DUR", dur);
1891 snprintf(passdata, passdatasize, "%d", msgnum);
1892 pbx_builtin_setvar_helper(ast, "VM_MSGNUM", passdata);
1893 pbx_builtin_setvar_helper(ast, "VM_CONTEXT", context);
1894 pbx_builtin_setvar_helper(ast, "VM_MAILBOX", mailbox);
1895 pbx_builtin_setvar_helper(ast, "VM_CALLERID", ast_callerid_merge(callerid, sizeof(callerid), cidname, cidnum, "Unknown Caller"));
1896 pbx_builtin_setvar_helper(ast, "VM_CIDNAME", (cidname ? cidname : "an unknown caller"));
1897 pbx_builtin_setvar_helper(ast, "VM_CIDNUM", (cidnum ? cidnum : "an unknown caller"));
1898 pbx_builtin_setvar_helper(ast, "VM_DATE", date);
1899 pbx_builtin_setvar_helper(ast, "VM_CATEGORY", category ? ast_strdupa(category) : "no category");
1902 static char *quote(const char *from, char *to, size_t len)
1906 for (; ptr < to + len - 1; from++) {
1909 else if (*from == '\0')
1913 if (ptr < to + len - 1)
1920 * fill in *tm for current time according to the proper timezone, if any.
1921 * Return tm so it can be used as a function argument.
1923 static const struct tm *vmu_tm(const struct ast_vm_user *vmu, struct tm *tm)
1925 const struct vm_zone *z = NULL;
1926 time_t t = time(NULL);
1928 /* Does this user have a timezone specified? */
1929 if (!ast_strlen_zero(vmu->zonetag)) {
1930 /* Find the zone in the list */
1931 AST_LIST_LOCK(&zones);
1932 AST_LIST_TRAVERSE(&zones, z, list) {
1933 if (!strcmp(z->name, vmu->zonetag))
1936 AST_LIST_UNLOCK(&zones);
1938 ast_localtime(&t, tm, z ? z->timezone : NULL);
1942 /*! \brief same as mkstemp, but return a FILE * */
1943 static FILE *vm_mkftemp(char *template)
1946 int pfd = mkstemp(template);
1948 p = fdopen(pfd, "w+");
1957 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)
1960 char host[MAXHOSTNAMELEN] = "";
1968 size_t len_passdata;
1975 gethostname(host, sizeof(host)-1);
1976 if (strchr(srcemail, '@'))
1977 ast_copy_string(who, srcemail, sizeof(who));
1979 snprintf(who, sizeof(who), "%s@%s", srcemail, host);
1980 snprintf(dur, sizeof(dur), "%d:%02d", duration / 60, duration % 60);
1981 strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", vmu_tm(vmu, &tm));
1982 fprintf(p, "Date: %s" ENDL, date);
1984 /* Set date format for voicemail mail */
1985 strftime(date, sizeof(date), emaildateformat, &tm);
1987 if (!ast_strlen_zero(fromstring)) {
1988 struct ast_channel *ast;
1989 if ((ast = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, 0))) {
1991 int vmlen = strlen(fromstring)*3 + 200;
1992 if ((passdata = alloca(vmlen))) {
1993 memset(passdata, 0, vmlen);
1994 prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category);
1995 pbx_substitute_variables_helper(ast, fromstring, passdata, vmlen);
1996 len_passdata = strlen(passdata) * 2 + 3;
1997 passdata2 = alloca(len_passdata);
1998 fprintf(p, "From: %s <%s>" ENDL, quote(passdata, passdata2, len_passdata), who);
2000 ast_log(LOG_WARNING, "Cannot allocate workspace for variable substitution\n");
2001 ast_channel_free(ast);
2003 ast_log(LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
2005 fprintf(p, "From: Asterisk PBX <%s>" ENDL, who);
2006 len_passdata = strlen(vmu->fullname) * 2 + 3;
2007 passdata2 = alloca(len_passdata);
2008 fprintf(p, "To: %s <%s>" ENDL, quote(vmu->fullname, passdata2, len_passdata), vmu->email);
2009 if (!ast_strlen_zero(emailsubject)) {
2010 struct ast_channel *ast;
2011 if ((ast = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, 0))) {
2013 int vmlen = strlen(emailsubject) * 3 + 200;
2014 if ((passdata = alloca(vmlen))) {
2015 memset(passdata, 0, vmlen);
2016 prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category);
2017 pbx_substitute_variables_helper(ast, emailsubject, passdata, vmlen);
2018 fprintf(p, "Subject: %s" ENDL, passdata);
2020 ast_log(LOG_WARNING, "Cannot allocate workspace for variable substitution\n");
2021 ast_channel_free(ast);
2023 ast_log(LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
2024 } else if (!ast_strlen_zero(emailtitle)) {
2025 fprintf(p, emailtitle, msgnum + 1, mailbox) ;
2027 } else if (ast_test_flag((&globalflags), VM_PBXSKIP))
2028 fprintf(p, "Subject: New message %d in mailbox %s" ENDL, msgnum + 1, mailbox);
2030 fprintf(p, "Subject: [PBX]: New message %d in mailbox %s" ENDL, msgnum + 1, mailbox);
2031 fprintf(p, "Message-ID: <Asterisk-%d-%d-%s-%d@%s>" ENDL, msgnum + 1, (unsigned int)ast_random(), mailbox, getpid(), host);
2033 /* additional information needed for IMAP searching */
2034 fprintf(p, "X-Asterisk-VM-Message-Num: %d" ENDL, msgnum + 1);
2035 /* fprintf(p, "X-Asterisk-VM-Orig-Mailbox: %s" ENDL, ext); */
2036 fprintf(p, "X-Asterisk-VM-Server-Name: %s" ENDL, fromstring);
2037 fprintf(p, "X-Asterisk-VM-Context: %s" ENDL, context);
2038 fprintf(p, "X-Asterisk-VM-Extension: %s" ENDL, mailbox);
2039 fprintf(p, "X-Asterisk-VM-Priority: %d" ENDL, chan->priority);
2040 fprintf(p, "X-Asterisk-VM-Caller-channel: %s" ENDL, chan->name);
2041 fprintf(p, "X-Asterisk-VM-Caller-ID-Num: %s" ENDL, cidnum);
2042 fprintf(p, "X-Asterisk-VM-Caller-ID-Name: %s" ENDL, cidname);
2043 fprintf(p, "X-Asterisk-VM-Duration: %d" ENDL, duration);
2044 if (!ast_strlen_zero(category))
2045 fprintf(p, "X-Asterisk-VM-Category: %s" ENDL, category);
2046 fprintf(p, "X-Asterisk-VM-Orig-date: %s" ENDL, date);
2047 fprintf(p, "X-Asterisk-VM-Orig-time: %ld" ENDL, (long)time(NULL));
2049 if (!ast_strlen_zero(cidnum))
2050 fprintf(p, "X-Asterisk-CallerID: %s" ENDL, cidnum);
2051 if (!ast_strlen_zero(cidname))
2052 fprintf(p, "X-Asterisk-CallerIDName: %s" ENDL, cidname);
2053 fprintf(p, "MIME-Version: 1.0" ENDL);
2054 if (attach_user_voicemail) {
2055 /* Something unique. */
2056 snprintf(bound, sizeof(bound), "----voicemail_%d%s%d%d", msgnum + 1, mailbox, getpid(), (unsigned int)ast_random());
2058 fprintf(p, "Content-Type: multipart/mixed; boundary=\"%s\"" ENDL, bound);
2059 fprintf(p, ENDL ENDL "This is a multi-part message in MIME format." ENDL ENDL);
2060 fprintf(p, "--%s" ENDL, bound);
2062 fprintf(p, "Content-Type: text/plain; charset=%s" ENDL "Content-Transfer-Encoding: 8bit" ENDL ENDL, charset);
2064 struct ast_channel *ast;
2065 if ((ast = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, 0))) {
2067 int vmlen = strlen(emailbody)*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, emailbody, passdata, vmlen);
2072 fprintf(p, "%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");
2079 fprintf(p, "Dear %s:" ENDL ENDL "\tJust wanted to let you know you were just left a %s long message (number %d)" ENDL
2081 "in mailbox %s from %s, on %s so you might" ENDL
2082 "want to check it when you get a chance. Thanks!" ENDL ENDL "\t\t\t\t--Asterisk" ENDL ENDL, vmu->fullname,
2083 dur, msgnum + 1, mailbox, (cidname ? cidname : (cidnum ? cidnum : "an unknown caller")), date);
2085 if (attach_user_voicemail) {
2086 /* Eww. We want formats to tell us their own MIME type */
2087 char *ctype = (!strcasecmp(format, "ogg")) ? "application/" : "audio/x-";
2088 char tmpdir[256], newtmp[256];
2091 if (vmu->volgain < -.001 || vmu->volgain > .001) {
2092 create_dirpath(tmpdir, sizeof(tmpdir), vmu->context, vmu->mailbox, "tmp");
2093 snprintf(newtmp, sizeof(newtmp), "%s/XXXXXX", tmpdir);
2094 tmpfd = mkstemp(newtmp);
2095 ast_debug(3, "newtmp: %s\n", newtmp);
2097 snprintf(tmpcmd, sizeof(tmpcmd), "sox -v %.4f %s.%s %s.%s", vmu->volgain, attach, format, newtmp, format);
2098 ast_safe_system(tmpcmd);
2100 ast_debug(3, "VOLGAIN: Stored at: %s.%s - Level: %.4f - Mailbox: %s\n", attach, format, vmu->volgain, mailbox);
2103 fprintf(p, "--%s" ENDL, bound);
2104 fprintf(p, "Content-Type: %s%s; name=\"msg%04d.%s\"" ENDL, ctype, format, msgnum + 1, format);
2105 fprintf(p, "Content-Transfer-Encoding: base64" ENDL);
2106 fprintf(p, "Content-Description: Voicemail sound attachment." ENDL);
2107 fprintf(p, "Content-Disposition: attachment; filename=\"msg%04d.%s\"" ENDL ENDL, msgnum + 1, format);
2108 snprintf(fname, sizeof(fname), "%s.%s", attach, format);
2109 base_encode(fname, p);
2110 fprintf(p, ENDL "--%s--" ENDL "." ENDL, bound);
2120 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)
2123 char tmp[80] = "/tmp/astmail-XXXXXX";
2126 if (vmu && ast_strlen_zero(vmu->email)) {
2127 ast_log(LOG_WARNING, "E-mail address missing for mailbox [%s]. E-mail will not be sent.\n", vmu->mailbox);
2130 if (!strcmp(format, "wav49"))
2132 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));
2133 /* Make a temporary file instead of piping directly to sendmail, in case the mail
2135 if ((p = vm_mkftemp(tmp)) == NULL) {
2136 ast_log(LOG_WARNING, "Unable to launch '%s' (can't create temporary file)\n", mailcmd);
2139 make_email_file(p, srcemail, vmu, msgnum, context, mailbox, cidnum, cidname, attach, format, duration, attach_user_voicemail, chan, category, 0);
2141 snprintf(tmp2, sizeof(tmp2), "( %s < %s ; rm -f %s ) &", mailcmd, tmp, tmp);
2142 ast_safe_system(tmp2);
2143 ast_debug(1, "Sent mail to %s with command '%s'\n", vmu->email, mailcmd);
2148 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)
2151 char host[MAXHOSTNAMELEN] = "";
2154 char tmp[80] = "/tmp/astmail-XXXXXX";
2155 char tmp2[PATH_MAX];
2159 if ((p = vm_mkftemp(tmp)) == NULL) {
2160 ast_log(LOG_WARNING, "Unable to launch '%s' (can't create temporary file)\n", mailcmd);
2163 gethostname(host, sizeof(host)-1);
2164 if (strchr(srcemail, '@'))
2165 ast_copy_string(who, srcemail, sizeof(who));
2167 snprintf(who, sizeof(who), "%s@%s", srcemail, host);
2168 snprintf(dur, sizeof(dur), "%d:%02d", duration / 60, duration % 60);
2169 strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", vmu_tm(vmu, &tm));
2170 fprintf(p, "Date: %s\n", date);
2172 if (*pagerfromstring) {
2173 struct ast_channel *ast;
2174 if ((ast = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, 0))) {
2176 int vmlen = strlen(fromstring)*3 + 200;
2177 if ((passdata = alloca(vmlen))) {
2178 memset(passdata, 0, vmlen);
2179 prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category);
2180 pbx_substitute_variables_helper(ast, pagerfromstring, passdata, vmlen);
2181 fprintf(p, "From: %s <%s>\n", passdata, who);
2183 ast_log(LOG_WARNING, "Cannot allocate workspace for variable substitution\n");
2184 ast_channel_free(ast);
2186 ast_log(LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
2188 fprintf(p, "From: Asterisk PBX <%s>\n", who);
2189 fprintf(p, "To: %s\n", pager);
2191 struct ast_channel *ast;
2192 if ((ast = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, 0))) {
2194 int vmlen = strlen(pagersubject) * 3 + 200;
2195 if ((passdata = alloca(vmlen))) {
2196 memset(passdata, 0, vmlen);
2197 prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category);
2198 pbx_substitute_variables_helper(ast, pagersubject, passdata, vmlen);
2199 fprintf(p, "Subject: %s\n\n", passdata);
2201 ast_log(LOG_WARNING, "Cannot allocate workspace for variable substitution\n");
2202 ast_channel_free(ast);
2204 ast_log(LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
2206 fprintf(p, "Subject: New VM\n\n");
2208 strftime(date, sizeof(date), "%A, %B %d, %Y at %r", &tm);
2210 struct ast_channel *ast;
2211 if ((ast = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, 0))) {
2213 int vmlen = strlen(pagerbody) * 3 + 200;
2214 passdata = alloca(vmlen);
2215 memset(passdata, 0, vmlen);
2216 prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category);
2217 pbx_substitute_variables_helper(ast, pagerbody, passdata, vmlen);
2218 fprintf(p, "%s\n", passdata);
2219 ast_channel_free(ast);
2221 ast_log(LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
2223 fprintf(p, "New %s long msg in box %s\n"
2224 "from %s, on %s", dur, mailbox, (cidname ? cidname : (cidnum ? cidnum : "unknown")), date);
2227 snprintf(tmp2, sizeof(tmp2), "( %s < %s ; rm -f %s ) &", mailcmd, tmp, tmp);
2228 ast_safe_system(tmp2);
2229 ast_debug(1, "Sent page to %s with command '%s'\n", pager, mailcmd);
2233 static int get_date(char *s, int len)
2238 ast_localtime(&t, &tm, NULL);
2239 return strftime(s, len, "%a %b %e %r %Z %Y", &tm);
2242 static int invent_message(struct ast_channel *chan, char *context, char *ext, int busy, char *ecodes)
2246 char dest[PATH_MAX];
2248 snprintf(fn, sizeof(fn), "%s%s/%s/greet", VM_SPOOL_DIR, context, ext);
2250 if ((res = create_dirpath(dest, sizeof(dest), context, ext, "greet"))) {
2251 ast_log(LOG_WARNING, "Failed to make directory(%s)\n", fn);
2256 if (ast_fileexists(fn, NULL, NULL) > 0) {
2257 res = ast_stream_and_wait(chan, fn, ecodes);
2263 /* Dispose just in case */
2265 res = ast_stream_and_wait(chan, "vm-theperson", ecodes);
2268 res = ast_say_digit_str(chan, ext, ecodes, chan->language);
2272 res = ast_stream_and_wait(chan, busy ? "vm-isonphone" : "vm-isunavail", ecodes);
2276 static void free_user(struct ast_vm_user *vmu)
2278 if (!ast_test_flag(vmu, VM_ALLOCED))
2284 static void free_zone(struct vm_zone *z)
2289 static const char *mbox(int id)
2291 static const char *msgs[] = {
2303 return (id >= 0 && id < (sizeof(msgs)/sizeof(msgs[0]))) ? msgs[id] : "Unknown";
2307 static int inboxcount(const char *mailbox, int *newmsgs, int *oldmsgs)
2314 char tmp[PATH_MAX] = "";
2315 struct odbc_obj *obj;
2323 /* If no mailbox, return immediately */
2324 if (ast_strlen_zero(mailbox))
2327 ast_copy_string(tmp, mailbox, sizeof(tmp));
2329 context = strchr(tmp, '@');
2334 context = "default";
2336 obj = ast_odbc_request_obj(odbc_database, 0);
2338 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
2339 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2340 ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
2341 ast_odbc_release_obj(obj);
2344 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir = '%s%s/%s/%s'", odbc_table, VM_SPOOL_DIR, context, tmp, "INBOX");
2345 res = SQLPrepare(stmt, (unsigned char *)sql, SQL_NTS);
2346 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2347 ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql);
2348 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2349 ast_odbc_release_obj(obj);
2352 res = ast_odbc_smart_execute(obj, stmt);
2353 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2354 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
2355 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2356 ast_odbc_release_obj(obj);
2359 res = SQLFetch(stmt);
2360 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2361 ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
2362 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2363 ast_odbc_release_obj(obj);
2366 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
2367 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2368 ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
2369 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2370 ast_odbc_release_obj(obj);
2373 *newmsgs = atoi(rowdata);
2374 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2376 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
2377 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2378 ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
2379 ast_odbc_release_obj(obj);
2382 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir = '%s%s/%s/%s'", odbc_table, VM_SPOOL_DIR, context, tmp, "Old");
2383 res = SQLPrepare(stmt, (unsigned char *)sql, SQL_NTS);
2384 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2385 ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql);
2386 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2387 ast_odbc_release_obj(obj);
2390 res = ast_odbc_smart_execute(obj, stmt);
2391 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2392 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
2393 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2394 ast_odbc_release_obj(obj);
2397 res = SQLFetch(stmt);
2398 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2399 ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
2400 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2401 ast_odbc_release_obj(obj);
2404 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
2405 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2406 ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
2407 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2408 ast_odbc_release_obj(obj);
2411 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2412 ast_odbc_release_obj(obj);
2413 *oldmsgs = atoi(rowdata);
2416 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
2422 static int messagecount(const char *context, const char *mailbox, const char *folder)
2424 struct odbc_obj *obj = NULL;
2427 SQLHSTMT stmt = NULL;
2432 /* If no mailbox, return immediately */
2433 if (ast_strlen_zero(mailbox))
2436 obj = ast_odbc_request_obj(odbc_database, 0);
2438 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
2439 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2440 ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
2443 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir = '%s%s/%s/%s'", odbc_table, VM_SPOOL_DIR, context, mailbox, folder);
2444 res = SQLPrepare(stmt, (unsigned char *)sql, SQL_NTS);
2445 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2446 ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql);
2447 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
2450 res = ast_odbc_smart_execute(obj, stmt);
2451 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2452 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
2453 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2456 res = SQLFetch(stmt);
2457 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2458 ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
2459 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2462 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
2463 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2464 ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
2465 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2468 nummsgs = atoi(rowdata);
2469 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2471 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
2475 ast_odbc_release_obj(obj);
2479 static int has_voicemail(const char *mailbox, const char *folder)
2481 char *context, tmp[256];
2482 ast_copy_string(tmp, mailbox, sizeof(tmp));
2483 if ((context = strchr(tmp, '@')))
2486 context = "default";
2488 if (messagecount(context, tmp, folder))
2494 #elif defined(IMAP_STORAGE)
2496 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)
2498 char *myserveremail = serveremail;
2503 char tmp[80] = "/tmp/astmail-XXXXXX";
2508 /* Attach only the first format */
2509 fmt = ast_strdupa(fmt);
2511 strsep(&stringp, "|");
2513 if (!ast_strlen_zero(vmu->serveremail))
2514 myserveremail = vmu->serveremail;
2516 make_file(fn, sizeof(fn), dir, msgnum);
2518 if (ast_strlen_zero(vmu->email))
2519 ast_copy_string(vmu->email, vmu->imapuser, sizeof(vmu->email));
2521 if (!strcmp(fmt, "wav49"))
2523 ast_debug(3, "Storing file '%s', format '%s'\n", fn, fmt);
2525 /* Make a temporary file instead of piping directly to sendmail, in case the mail
2527 if (!(p = vm_mkftemp(tmp))) {
2528 ast_log(LOG_WARNING, "Unable to store '%s' (can't create temporary file)\n", fn);
2532 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);
2533 /* read mail file to memory */
2536 if (!(buf = ast_malloc(len+1))) {
2537 ast_log(LOG_ERROR, "Can't allocate %ld bytes to read message\n", len+1);
2540 fread(buf, len, 1, p);
2541 ((char *)buf)[len] = '\0';
2542 INIT(&str, mail_string, buf, len);
2543 init_mailstream(vms, 0);
2544 imap_mailbox_name(mailbox, vms, 0, 1);
2545 if(!mail_append(vms->mailstream, mailbox, &str))
2546 ast_log(LOG_ERROR, "Error while sending the message to %s\n", mailbox);
2550 ast_debug(3, "%s stored\n", fn);
2555 static int inboxcount(const char *mailbox, int *newmsgs, int *oldmsgs)
2560 struct ast_vm_user *vmu;
2561 struct vm_state *vms_p;
2574 ast_debug(3,"Mailbox is set to %s\n",mailbox);
2576 /* If no mailbox, return immediately */
2577 if (ast_strlen_zero(mailbox))
2580 if (strchr(mailbox, ',')) {
2582 ast_copy_string(tmp, mailbox, sizeof(tmp));
2585 while((cur = strsep(&mb, ", "))) {
2586 if (!ast_strlen_zero(cur)) {
2587 if (inboxcount(cur, newmsgs ? &tmpnew : NULL, oldmsgs ? &tmpold : NULL))
2600 ast_copy_string(tmp, mailbox, sizeof(tmp));
2602 if ((context = strchr(tmp, '@'))) {
2607 context = "default";
2608 mailboxnc = (char *)mailbox;
2611 /* We have to get the user before we can open the stream! */
2612 /*ast_debug(1,"Before find_user, context is %s and mailbox is %s\n",context,mailbox); */
2613 if (!(vmu = find_user(NULL, context, mailboxnc))) {
2614 ast_log(LOG_ERROR, "Couldn't find mailbox %s in context %s\n", mailboxnc, context);
2618 /* No IMAP account available */
2619 if (vmu->imapuser[0] == '\0') {
2620 ast_log (LOG_WARNING,"IMAP user not set for mailbox %s\n",vmu->mailbox);
2625 /* check if someone is accessing this box right now... */
2626 if ((vms_p = get_vm_state_by_imapuser(vmu->imapuser, 1)) || (vms_p = get_vm_state_by_mailbox(mailboxnc, 1))) {
2627 ast_debug(3,"Returning before search - user is logged in\n");
2628 *newmsgs = vms_p->newmessages;
2629 *oldmsgs = vms_p->oldmessages;
2634 /* add one if not there... */
2635 if (!(vms_p = get_vm_state_by_imapuser(vmu->imapuser, 0)) && !(vms_p = get_vm_state_by_mailbox(mailboxnc, 0))) {
2636 ast_debug(3,"Adding new vmstate for %s\n",vmu->imapuser);
2637 if (!(vms_p = ast_calloc(1, sizeof(*vms_p)))) {
2641 ast_copy_string(vms_p->imapuser,vmu->imapuser, sizeof(vms_p->imapuser));
2642 ast_copy_string(vms_p->username, mailboxnc, sizeof(vms_p->username)); /* save for access from interactive entry point */
2643 vms_p->mailstream = NIL; /* save for access from interactive entry point */
2644 ast_debug(3,"Copied %s to %s\n",vmu->imapuser,vms_p->imapuser);
2646 /* set mailbox to INBOX! */
2647 ast_copy_string(vms_p->curbox, mbox(0), sizeof(vms_p->curbox));
2648 init_vm_state(vms_p);
2649 vmstate_insert(vms_p);
2652 /* If no mailstream exists yet and even after attempting to initialize it fails, bail out */
2653 ret = init_mailstream(vms_p, 0);
2654 if (!vms_p->mailstream) {
2655 ast_log (LOG_ERROR,"Houston we have a problem - IMAP mailstream is NULL\n");
2660 if (!ret && vms_p->updated == 1) {
2662 pgm = mail_newsearchpgm();
2663 hdr = mail_newsearchheader("X-Asterisk-VM-Extension", (char *)mailboxnc);
2669 vms_p->vmArrayIndex = 0;
2670 mail_search_full(vms_p->mailstream, NULL, pgm, NIL);
2671 *newmsgs = vms_p->vmArrayIndex;
2672 vms_p->newmessages = vms_p->vmArrayIndex;
2673 mail_free_searchpgm(&pgm);
2676 pgm = mail_newsearchpgm ();
2677 hdr = mail_newsearchheader ("X-Asterisk-VM-Extension", (char *)mailboxnc);
2683 vms_p->vmArrayIndex = 0;
2684 mail_search_full(vms_p->mailstream, NULL, pgm, NIL);
2685 *oldmsgs = vms_p->vmArrayIndex;
2686 vms_p->oldmessages = vms_p->vmArrayIndex;
2687 mail_free_searchpgm(&pgm);
2691 if (vms_p->updated == 1) { /* changes, so we did the searches above */
2693 } else if (vms_p->updated > 1) { /* decrement delay count */
2695 } else { /* no changes, so don't search */
2696 mail_ping(vms_p->mailstream);
2697 /* Keep the old data */
2698 *newmsgs = vms_p->newmessages;
2699 *oldmsgs = vms_p->oldmessages;
2706 static int has_voicemail(const char *mailbox, const char *folder)
2708 int newmsgs, oldmsgs;
2710 if(inboxcount(mailbox, &newmsgs, &oldmsgs))
2711 return folder? oldmsgs: newmsgs;
2716 static int messagecount(const char *context, const char *mailbox, const char *folder)
2718 int newmsgs, oldmsgs;
2721 if (ast_strlen_zero(mailbox))
2723 sprintf(tmp,"%s@%s", mailbox, ast_strlen_zero(context)? "default": context);
2725 if(inboxcount(tmp, &newmsgs, &oldmsgs))
2726 return folder? oldmsgs: newmsgs;
2732 #ifndef IMAP_STORAGE
2733 /* copy message only used by file storage */
2734 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)
2736 char fromdir[PATH_MAX], todir[PATH_MAX], frompath[PATH_MAX], topath[PATH_MAX];
2737 const char *frombox = mbox(imbox);
2740 ast_log(LOG_NOTICE, "Copying message from %s@%s to %s@%s\n", vmu->mailbox, vmu->context, recip->mailbox, recip->context);
2742 create_dirpath(todir, sizeof(todir), recip->context, recip->mailbox, "INBOX");
2745 make_dir(fromdir, sizeof(fromdir), vmu->context, vmu->mailbox, frombox);
2747 ast_copy_string(fromdir, dir, sizeof(fromdir));
2749 make_file(frompath, sizeof(frompath), fromdir, msgnum);
2750 make_dir(todir, sizeof(todir), recip->context, recip->mailbox, frombox);
2752 if (vm_lock_path(todir))
2753 return ERROR_LOCK_PATH;
2755 recipmsgnum = last_message_index(recip, todir) + 1;
2756 if (recipmsgnum < recip->maxmsg) {
2757 make_file(topath, sizeof(topath), todir, recipmsgnum);
2758 COPY(fromdir, msgnum, todir, recipmsgnum, recip->mailbox, recip->context, frompath, topath);
2760 ast_log(LOG_ERROR, "Recipient mailbox %s@%s is full\n", recip->mailbox, recip->context);
2762 ast_unlock_path(todir);
2763 notify_new_message(chan, recip, recipmsgnum, duration, fmt, S_OR(chan->cid.cid_num, NULL), S_OR(chan->cid.cid_name, NULL));
2768 #if !(defined(IMAP_STORAGE) || defined(ODBC_STORAGE))
2769 static int messagecount(const char *context, const char *mailbox, const char *folder)
2771 return __has_voicemail(context, mailbox, folder, 0);
2775 static int __has_voicemail(const char *context, const char *mailbox, const char *folder, int shortcircuit)
2782 /* If no mailbox, return immediately */
2783 if (ast_strlen_zero(mailbox))
2786 if (ast_strlen_zero(folder))
2788 if (ast_strlen_zero(context))
2789 context = "default";
2791 snprintf(fn, sizeof(fn), "%s%s/%s/%s", VM_SPOOL_DIR, context, mailbox, folder);
2793 if (!(dir = opendir(fn)))
2796 while ((de = readdir(dir))) {
2797 if (!strncasecmp(de->d_name, "msg", 3)) {
2801 } else if (!strncasecmp(de->d_name + 8, "txt", 3))
2812 static int has_voicemail(const char *mailbox, const char *folder)
2814 char tmp[256], *tmp2 = tmp, *mbox, *context;
2815 ast_copy_string(tmp, mailbox, sizeof(tmp));
2816 while ((mbox = strsep(&tmp2, ","))) {
2817 if ((context = strchr(mbox, '@')))
2820 context = "default";
2821 if (__has_voicemail(context, mbox, folder, 1))
2828 static int inboxcount(const char *mailbox, int *newmsgs, int *oldmsgs)
2833 /* If no mailbox, return immediately */
2834 if (ast_strlen_zero(mailbox))
2842 if (strchr(mailbox, ',')) {
2846 ast_copy_string(tmp, mailbox, sizeof(tmp));
2848 while ((cur = strsep(&mb, ", "))) {
2849 if (!ast_strlen_zero(cur)) {
2850 if (inboxcount(cur, newmsgs ? &tmpnew : NULL, oldmsgs ? &tmpold : NULL))
2863 ast_copy_string(tmp, mailbox, sizeof(tmp));
2865 if ((context = strchr(tmp, '@')))
2868 context = "default";
2871 *newmsgs = __has_voicemail(context, tmp, "INBOX", 0);
2873 *oldmsgs = __has_voicemail(context, tmp, "Old", 0);
2880 static void run_externnotify(char *context, char *extension)
2882 char arguments[255];
2883 char ext_context[256] = "";
2884 int newvoicemails = 0, oldvoicemails = 0;
2885 struct ast_smdi_mwi_message *mwi_msg;
2887 if (!ast_strlen_zero(context))
2888 snprintf(ext_context, sizeof(ext_context), "%s@%s", extension, context);
2890 ast_copy_string(ext_context, extension, sizeof(ext_context));
2893 if (ast_app_has_voicemail(ext_context, NULL))
2894 ast_smdi_mwi_set(smdi_iface, extension);
2896 ast_smdi_mwi_unset(smdi_iface, extension);
2898 if ((mwi_msg = ast_smdi_mwi_message_wait(smdi_iface, SMDI_MWI_WAIT_TIMEOUT))) {
2899 ast_log(LOG_ERROR, "Error executing SMDI MWI change for %s on %s\n", extension, smdi_iface->name);
2900 if (!strncmp(mwi_msg->cause, "INV", 3))
2901 ast_log(LOG_ERROR, "Invalid MWI extension: %s\n", mwi_msg->fwd_st);
2902 else if (!strncmp(mwi_msg->cause, "BLK", 3))
2903 ast_log(LOG_WARNING, "MWI light was already on or off for %s\n", mwi_msg->fwd_st);
2904 ast_log(LOG_WARNING, "The switch reported '%s'\n", mwi_msg->cause);
2905 ASTOBJ_UNREF(mwi_msg, ast_smdi_mwi_message_destroy);
2907 ast_debug(1, "Successfully executed SMDI MWI change for %s on %s\n", extension, smdi_iface->name);
2911 if (!ast_strlen_zero(externnotify)) {
2912 if (inboxcount(ext_context, &newvoicemails, &oldvoicemails)) {
2913 ast_log(LOG_ERROR, "Problem in calculating number of voicemail messages available for extension %s\n", extension);
2915 snprintf(arguments, sizeof(arguments), "%s %s %s %d&", externnotify, context, extension, newvoicemails);
2916 ast_debug(1, "Executing %s\n", arguments);
2917 ast_safe_system(arguments);
2922 struct leave_vm_options {
2924 signed char record_gain;
2927 static int leave_voicemail(struct ast_channel *chan, char *ext, struct leave_vm_options *options)
2930 int newmsgs, oldmsgs;
2931 struct vm_state *vms = NULL;
2933 char txtfile[PATH_MAX], tmptxtfile[PATH_MAX];
2949 char dir[PATH_MAX], tmpdir[PATH_MAX];
2950 char dest[PATH_MAX];
2952 char prefile[PATH_MAX] = "";
2953 char tempfile[PATH_MAX] = "";
2954 char ext_context[256] = "";
2957 char ecodes[16] = "#";
2958 char tmp[1024] = "", *tmpptr;
2959 struct ast_vm_user *vmu;
2960 struct ast_vm_user svm;
2961 const char *category = NULL;
2963 ast_copy_string(tmp, ext, sizeof(tmp));
2965 if ((context = strchr(tmp, '@'))) {
2967 tmpptr = strchr(context, '&');
2969 tmpptr = strchr(ext, '&');
2975 category = pbx_builtin_getvar_helper(chan, "VM_CATEGORY");
2977 ast_debug(3, "Before find_user\n");
2978 if (!(vmu = find_user(&svm, context, ext))) {
2979 ast_log(LOG_WARNING, "No entry in voicemail config file for '%s'\n", ext);
2980 pbx_builtin_setvar_helper(chan, "VMSTATUS", "FAILED");
2983 /* Setup pre-file if appropriate */
2984 if (strcmp(vmu->context, "default"))
2985 snprintf(ext_context, sizeof(ext_context), "%s@%s", ext, vmu->context);
2987 ast_copy_string(ext_context, vmu->context, sizeof(ext_context));
2988 if (ast_test_flag(options, OPT_BUSY_GREETING)) {
2989 res = create_dirpath(dest, sizeof(dest), vmu->context, ext, "busy");
2990 snprintf(prefile, sizeof(prefile), "%s%s/%s/busy", VM_SPOOL_DIR, vmu->context, ext);
2991 } else if (ast_test_flag(options, OPT_UNAVAIL_GREETING)) {
2992 res = create_dirpath(dest, sizeof(dest), vmu->context, ext, "unavail");
2993 snprintf(prefile, sizeof(prefile), "%s%s/%s/unavail", VM_SPOOL_DIR, vmu->context, ext);
2995 snprintf(tempfile, sizeof(tempfile), "%s%s/%s/temp", VM_SPOOL_DIR, vmu->context, ext);
2996 if ((res = create_dirpath(dest, sizeof(dest), vmu->context, ext, "temp"))) {
2997 ast_log(LOG_WARNING, "Failed to make directory (%s)\n", tempfile);
3000 RETRIEVE(tempfile, -1);
3001 if (ast_fileexists(tempfile, NULL, NULL) > 0)
3002 ast_copy_string(prefile, tempfile, sizeof(prefile));
3003 DISPOSE(tempfile, -1);
3004 /* It's easier just to try to make it than to check for its existence */
3005 create_dirpath(dir, sizeof(dir), vmu->context, ext, "INBOX");
3006 create_dirpath(tmpdir, sizeof(tmpdir), vmu->context, ext, "tmp");
3008 /* Check current or macro-calling context for special extensions */
3009 if (ast_test_flag(vmu, VM_OPERATOR)) {
3010 if (!ast_strlen_zero(vmu->exit)) {
3011 if (ast_exists_extension(chan, vmu->exit, "o", 1, chan->cid.cid_num)) {
3012 strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
3015 } else if (ast_exists_extension(chan, chan->context, "o", 1, chan->cid.cid_num)) {
3016 strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
3019 else if (!ast_strlen_zero(chan->macrocontext) && ast_exists_extension(chan, chan->macrocontext, "o", 1, chan->cid.cid_num)) {
3020 strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
3025 if (!ast_strlen_zero(vmu->exit)) {
3026 if (ast_exists_extension(chan, vmu->exit, "a", 1, chan->cid.cid_num))
3027 strncat(ecodes, "*", sizeof(ecodes) - strlen(ecodes) - 1);
3028 } else if (ast_exists_extension(chan, chan->context, "a", 1, chan->cid.cid_num))
3029 strncat(ecodes, "*", sizeof(ecodes) - strlen(ecodes) - 1);
3030 else if (!ast_strlen_zero(chan->macrocontext) && ast_exists_extension(chan, chan->macrocontext, "a", 1, chan->cid.cid_num)) {
3031 strncat(ecodes, "*", sizeof(ecodes) - strlen(ecodes) - 1);
3035 /* Play the beginning intro if desired */
3036 if (!ast_strlen_zero(prefile)) {
3037 RETRIEVE(prefile, -1);
3038 if (ast_fileexists(prefile, NULL, NULL) > 0) {
3039 if (ast_streamfile(chan, prefile, chan->language) > -1)
3040 res = ast_waitstream(chan, ecodes);
3042 ast_debug(1, "%s doesn't exist, doing what we can\n", prefile);
3043 res = invent_message(chan, vmu->context, ext, ast_test_flag(options, OPT_BUSY_GREETING), ecodes);
3045 DISPOSE(prefile, -1);
3047 ast_debug(1, "Hang up during prefile playback\n");
3049 pbx_builtin_setvar_helper(chan, "VMSTATUS", "FAILED");
3054 /* On a '#' we skip the instructions */
3055 ast_set_flag(options, OPT_SILENT);
3058 if (!res && !ast_test_flag(options, OPT_SILENT)) {
3059 res = ast_stream_and_wait(chan, INTRO, ecodes);