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>
27 * \ingroup applications
28 * \note This module requires res_adsi to load.
32 <category name="MENUSELECT_OPTS_app_voicemail" displayname="Voicemail Build Options" positive_output="yes" remove_on_change="apps/app_voicemail.o">
33 <member name="ODBC_STORAGE" displayname="Storage of Voicemail using ODBC">
34 <depend>unixodbc</depend>
35 <defaultenabled>no</defaultenabled>
37 <member name="IMAP_STORAGE" displayname="Storage of Voicemail using IMAP4">
38 <depend>imap_tk</depend>
40 <defaultenabled>no</defaultenabled>
47 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
57 #include <sys/types.h>
69 #include "asterisk/lock.h"
70 #include "asterisk/file.h"
71 #include "asterisk/logger.h"
72 #include "asterisk/channel.h"
73 #include "asterisk/pbx.h"
74 #include "asterisk/options.h"
75 #include "asterisk/config.h"
76 #include "asterisk/say.h"
77 #include "asterisk/module.h"
78 #include "asterisk/adsi.h"
79 #include "asterisk/app.h"
80 #include "asterisk/manager.h"
81 #include "asterisk/dsp.h"
82 #include "asterisk/localtime.h"
83 #include "asterisk/cli.h"
84 #include "asterisk/utils.h"
85 #include "asterisk/stringfields.h"
86 #include "asterisk/smdi.h"
88 #include "asterisk/res_odbc.h"
92 AST_MUTEX_DEFINE_STATIC(curhstusr_lock);
93 static char *curhst = NIL; /* currently connected host */
94 static char *curusr = NIL; /* current login user */
96 static char temp[1024];
98 static char imapserver[48];
99 static char imapport[8];
100 static char imapflags[128];
101 static char authuser[32];
102 static char authpassword[42];
103 static int expungeonhangup = 1;
104 static char delimiter = '\0';
108 static int init_mailstream (struct vm_state *vms);
109 static void write_file (char *filename, char *buffer, unsigned long len);
110 static void status (MAILSTREAM *stream);
111 static void display_body (BODY *body, char *pfx, long i);
112 static char *get_header_by_tag(char *header, char *tag);
113 static void vm_imap_delete(int msgnum, struct vm_state *vms);
114 static char *get_user_by_mailbox(char *mailbox);
115 static struct vm_state *get_vm_state_by_imapuser(char *user, int interactive);
116 static struct vm_state *get_vm_state_by_mailbox(const char *mailbox, int interactive);
117 static void vmstate_insert(struct vm_state *vms);
118 static void vmstate_delete(struct vm_state *vms);
119 static void set_update(MAILSTREAM * stream);
120 static void init_vm_state(struct vm_state *vms);
121 static void check_msgArray(struct vm_state *vms);
122 static void copy_msgArray(struct vm_state *dst, struct vm_state *src);
123 static int save_body(BODY *body, struct vm_state *vms, char *section, char *format);
124 static int make_gsm_file(char *dest, char *imapuser, char *dir, int num);
125 static void get_mailbox_delimiter(MAILSTREAM *stream);
126 static void mm_parsequota (MAILSTREAM *stream, unsigned char *msg, QUOTALIST *pquota);
128 struct vm_state *vms;
129 struct vmstate *next;
131 AST_MUTEX_DEFINE_STATIC(vmstate_lock);
132 static struct vmstate *vmstates = NULL;
135 #define SMDI_MWI_WAIT_TIMEOUT 1000 /* 1 second */
137 #define COMMAND_TIMEOUT 5000
138 /* Don't modify these here; set your umask at runtime instead */
139 #define VOICEMAIL_DIR_MODE 0777
140 #define VOICEMAIL_FILE_MODE 0666
142 #define VOICEMAIL_CONFIG "voicemail.conf"
143 #define ASTERISK_USERNAME "asterisk"
145 /* Default mail command to mail voicemail. Change it with the
146 mailcmd= command in voicemail.conf */
147 #define SENDMAIL "/usr/sbin/sendmail -t"
149 #define INTRO "vm-intro"
152 #define MAXMSGLIMIT 9999
154 #define BASEMAXINLINE 256
155 #define BASELINELEN 72
156 #define BASEMAXINLINE 256
159 #define MAX_DATETIME_FORMAT 512
160 #define MAX_NUM_CID_CONTEXTS 10
162 #define VM_REVIEW (1 << 0)
163 #define VM_OPERATOR (1 << 1)
164 #define VM_SAYCID (1 << 2)
165 #define VM_SVMAIL (1 << 3)
166 #define VM_ENVELOPE (1 << 4)
167 #define VM_SAYDURATION (1 << 5)
168 #define VM_SKIPAFTERCMD (1 << 6)
169 #define VM_FORCENAME (1 << 7) /*!< Have new users record their name */
170 #define VM_FORCEGREET (1 << 8) /*!< Have new users record their greetings */
171 #define VM_PBXSKIP (1 << 9)
172 #define VM_DIRECFORWARD (1 << 10) /*!< directory_forward */
173 #define VM_ATTACH (1 << 11)
174 #define VM_DELETE (1 << 12)
175 #define VM_ALLOCED (1 << 13)
176 #define VM_SEARCH (1 << 14)
177 #define VM_TEMPGREETWARN (1 << 15) /*!< Remind user tempgreeting is set */
178 #define ERROR_LOCK_PATH -100
182 OPT_SILENT = (1 << 0),
183 OPT_BUSY_GREETING = (1 << 1),
184 OPT_UNAVAIL_GREETING = (1 << 2),
185 OPT_RECORDGAIN = (1 << 3),
186 OPT_PREPEND_MAILBOX = (1 << 4),
187 OPT_PRIORITY_JUMP = (1 << 5),
188 OPT_AUTOPLAY = (1 << 6),
192 OPT_ARG_RECORDGAIN = 0,
193 OPT_ARG_PLAYFOLDER = 1,
194 /* This *must* be the last value in this enum! */
195 OPT_ARG_ARRAY_SIZE = 2,
198 AST_APP_OPTIONS(vm_app_options, {
199 AST_APP_OPTION('s', OPT_SILENT),
200 AST_APP_OPTION('b', OPT_BUSY_GREETING),
201 AST_APP_OPTION('u', OPT_UNAVAIL_GREETING),
202 AST_APP_OPTION_ARG('g', OPT_RECORDGAIN, OPT_ARG_RECORDGAIN),
203 AST_APP_OPTION('p', OPT_PREPEND_MAILBOX),
204 AST_APP_OPTION('j', OPT_PRIORITY_JUMP),
205 AST_APP_OPTION_ARG('a', OPT_AUTOPLAY, OPT_ARG_PLAYFOLDER),
208 static int load_config(void);
210 /*! \page vmlang Voicemail Language Syntaxes Supported
212 \par Syntaxes supported, not really language codes.
220 \arg \b pt - Portuguese
222 \arg \b no - Norwegian
225 German requires the following additional soundfile:
226 \arg \b 1F einE (feminine)
228 Spanish requires the following additional soundfile:
229 \arg \b 1M un (masculine)
231 Dutch, Portuguese & Spanish require the following additional soundfiles:
232 \arg \b vm-INBOXs singular of 'new'
233 \arg \b vm-Olds singular of 'old/heard/read'
236 \arg \b vm-INBOX nieuwe (nl)
237 \arg \b vm-Old oude (nl)
240 \arg \b vm-new-a 'new', feminine singular accusative
241 \arg \b vm-new-e 'new', feminine plural accusative
242 \arg \b vm-new-ych 'new', feminine plural genitive
243 \arg \b vm-old-a 'old', feminine singular accusative
244 \arg \b vm-old-e 'old', feminine plural accusative
245 \arg \b vm-old-ych 'old', feminine plural genitive
246 \arg \b digits/1-a 'one', not always same as 'digits/1'
247 \arg \b digits/2-ie 'two', not always same as 'digits/2'
250 \arg \b vm-nytt singular of 'new'
251 \arg \b vm-nya plural of 'new'
252 \arg \b vm-gammalt singular of 'old'
253 \arg \b vm-gamla plural of 'old'
254 \arg \b digits/ett 'one', not always same as 'digits/1'
257 \arg \b vm-ny singular of 'new'
258 \arg \b vm-nye plural of 'new'
259 \arg \b vm-gammel singular of 'old'
260 \arg \b vm-gamle plural of 'old'
268 Italian requires the following additional soundfile:
272 \arg \b vm-nuovi new plural
273 \arg \b vm-vecchio old
274 \arg \b vm-vecchi old plural
276 \note Don't use vm-INBOX or vm-Old, because they are the name of the INBOX and Old folders,
277 spelled among others when you have to change folder. For the above reasons, vm-INBOX
278 and vm-Old are spelled plural, to make them sound more as folder name than an adjective.
287 unsigned char iobuf[BASEMAXINLINE];
290 /*! Structure for linked list of users */
292 char context[AST_MAX_CONTEXT]; /*!< Voicemail context */
293 char mailbox[AST_MAX_EXTENSION]; /*!< Mailbox id, unique within vm context */
294 char password[80]; /*!< Secret pin code, numbers only */
295 char fullname[80]; /*!< Full name, for directory app */
296 char email[80]; /*!< E-mail address */
297 char pager[80]; /*!< E-mail address to pager (no attachment) */
298 char serveremail[80]; /*!< From: Mail address */
299 char mailcmd[160]; /*!< Configurable mail command */
300 char language[MAX_LANGUAGE]; /*!< Config: Language setting */
301 char zonetag[80]; /*!< Time zone */
304 char uniqueid[20]; /*!< Unique integer identifier */
306 char attachfmt[20]; /*!< Attachment format */
307 unsigned int flags; /*!< VM_ flags */
309 int maxmsg; /*!< Maximum number of msgs per folder for this mailbox */
311 char imapuser[80]; /* IMAP server login */
313 double volgain; /*!< Volume gain for voicemails sent via email */
314 AST_LIST_ENTRY(ast_vm_user) list;
318 AST_LIST_ENTRY(vm_zone) list;
321 char msg_format[512];
340 int updated; /* decremented on each mail check until 1 -allows delay */
342 MAILSTREAM *mailstream;
344 char imapuser[80]; /* IMAP server login */
346 unsigned int quota_limit;
347 unsigned int quota_usage;
348 struct vm_state *persist_vms;
351 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);
352 static int dialout(struct ast_channel *chan, struct ast_vm_user *vmu, char *num, char *outgoing_context);
353 static int play_record_review(struct ast_channel *chan, char *playfile, char *recordfile, int maxtime,
354 char *fmt, int outsidecaller, struct ast_vm_user *vmu, int *duration, const char *unlockdir,
355 signed char record_gain);
356 static int vm_tempgreeting(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, char *fmtc, signed char record_gain);
357 static int vm_play_folder_name(struct ast_channel *chan, char *mbox);
358 static int notify_new_message(struct ast_channel *chan, struct ast_vm_user *vmu, int msgnum, long duration, char *fmt, char *cidnum, char *cidname);
360 static int __has_voicemail(const char *context, const char *mailbox, const char *folder, int shortcircuit);
362 static void apply_options(struct ast_vm_user *vmu, const char *options);
365 static char odbc_database[80];
366 static char odbc_table[80];
367 #define RETRIEVE(a,b) retrieve_file(a,b)
368 #define DISPOSE(a,b) remove_file(a,b)
369 #define STORE(a,b,c,d) store_file(a,b,c,d)
370 #define EXISTS(a,b,c,d) (message_exists(a,b))
371 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(a,b,c,d,e,f))
372 #define COPY(a,b,c,d,e,f,g,h) (copy_file(a,b,c,d,e,f))
373 #define DELETE(a,b,c) (delete_file(a,b))
376 #define RETRIEVE(a,b)
378 #define STORE(a,b,c,d)
379 #define EXISTS(a,b,c,d) (ast_fileexists(c,NULL,d) > 0)
380 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(g,h));
381 #define COPY(a,b,c,d,e,f,g,h) (copy_file(g,h));
382 #define IMAP_DELETE(a,b,c,d) (vm_imap_delete(b,d))
383 #define DELETE(a,b,c) (vm_delete(c))
385 #define RETRIEVE(a,b)
387 #define STORE(a,b,c,d)
388 #define EXISTS(a,b,c,d) (ast_fileexists(c,NULL,d) > 0)
389 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(g,h));
390 #define COPY(a,b,c,d,e,f,g,h) (copy_file(g,h));
391 #define DELETE(a,b,c) (vm_delete(c))
395 static char VM_SPOOL_DIR[PATH_MAX];
397 static char ext_pass_cmd[128];
400 #define tdesc "Comedian Mail (Voicemail System) with ODBC Storage"
402 #define tdesc "Comedian Mail (Voicemail System) with IMAP Storage"
404 #define tdesc "Comedian Mail (Voicemail System)"
407 static char userscontext[AST_MAX_EXTENSION] = "default";
409 static char *addesc = "Comedian Mail";
411 static char *synopsis_vm =
412 "Leave a Voicemail message";
414 static char *descrip_vm =
415 " VoiceMail(mailbox[@context][&mailbox[@context]][...][|options]): This\n"
416 "application allows the calling party to leave a message for the specified\n"
417 "list of mailboxes. When multiple mailboxes are specified, the greeting will\n"
418 "be taken from the first mailbox specified. Dialplan execution will stop if the\n"
419 "specified mailbox does not exist.\n"
420 " The Voicemail application will exit if any of the following DTMF digits are\n"
422 " 0 - Jump to the 'o' extension in the current dialplan context.\n"
423 " * - Jump to the 'a' extension in the current dialplan context.\n"
424 " This application will set the following channel variable upon completion:\n"
425 " VMSTATUS - This indicates the status of the execution of the VoiceMail\n"
426 " application. The possible values are:\n"
427 " SUCCESS | USEREXIT | FAILED\n\n"
429 " b - Play the 'busy' greeting to the calling party.\n"
430 " g(#) - Use the specified amount of gain when recording the voicemail\n"
431 " message. The units are whole-number decibels (dB).\n"
432 " s - Skip the playback of instructions for leaving a message to the\n"
434 " u - Play the 'unavailble greeting.\n"
435 " j - Jump to priority n+101 if the mailbox is not found or some other\n"
438 static char *synopsis_vmain =
439 "Check Voicemail messages";
441 static char *descrip_vmain =
442 " VoiceMailMain([mailbox][@context][|options]): This application allows the\n"
443 "calling party to check voicemail messages. A specific mailbox, and optional\n"
444 "corresponding context, may be specified. If a mailbox is not provided, the\n"
445 "calling party will be prompted to enter one. If a context is not specified,\n"
446 "the 'default' context will be used.\n\n"
448 " p - Consider the mailbox parameter as a prefix to the mailbox that\n"
449 " is entered by the caller.\n"
450 " g(#) - Use the specified amount of gain when recording a voicemail\n"
451 " message. The units are whole-number decibels (dB).\n"
452 " s - Skip checking the passcode for the mailbox.\n"
453 " a(#) - Skip folder prompt and go directly to folder specified.\n"
454 " Defaults to INBOX\n";
456 static char *synopsis_vm_box_exists =
457 "Check to see if Voicemail mailbox exists";
459 static char *descrip_vm_box_exists =
460 " MailboxExists(mailbox[@context][|options]): Check to see if the specified\n"
461 "mailbox exists. If no voicemail context is specified, the 'default' context\n"
463 " This application will set the following channel variable upon completion:\n"
464 " VMBOXEXISTSSTATUS - This will contain the status of the execution of the\n"
465 " MailboxExists application. Possible values include:\n"
466 " SUCCESS | FAILED\n\n"
468 " j - Jump to priority n+101 if the mailbox is found.\n";
470 static char *synopsis_vmauthenticate =
471 "Authenticate with Voicemail passwords";
473 static char *descrip_vmauthenticate =
474 " VMAuthenticate([mailbox][@context][|options]): This application behaves the\n"
475 "same way as the Authenticate application, but the passwords are taken from\n"
477 " If the mailbox is specified, only that mailbox's password will be considered\n"
478 "valid. If the mailbox is not specified, the channel variable AUTH_MAILBOX will\n"
479 "be set with the authenticated mailbox.\n\n"
481 " s - Skip playing the initial prompts.\n";
483 /* Leave a message */
484 static char *app = "VoiceMail";
486 /* Check mail, control, etc */
487 static char *app2 = "VoiceMailMain";
489 static char *app3 = "MailboxExists";
490 static char *app4 = "VMAuthenticate";
492 static AST_LIST_HEAD_STATIC(users, ast_vm_user);
493 static AST_LIST_HEAD_STATIC(zones, vm_zone);
494 static int maxsilence;
496 static int silencethreshold = 128;
497 static char serveremail[80];
498 static char mailcmd[160]; /* Configurable mail cmd */
499 static char externnotify[160];
500 static struct ast_smdi_interface *smdi_iface = NULL;
501 static char vmfmts[80];
502 static double volgain;
503 static int vmminmessage;
504 static int vmmaxmessage;
507 static int maxlogins;
509 static struct ast_flags globalflags = {0};
511 static int saydurationminfo;
513 static char dialcontext[AST_MAX_CONTEXT];
514 static char callcontext[AST_MAX_CONTEXT];
515 static char exitcontext[AST_MAX_CONTEXT];
517 static char cidinternalcontexts[MAX_NUM_CID_CONTEXTS][64];
520 static char *emailbody = NULL;
521 static char *emailsubject = NULL;
522 static char *pagerbody = NULL;
523 static char *pagersubject = NULL;
524 static char fromstring[100];
525 static char pagerfromstring[100];
526 static char emailtitle[100];
527 static char charset[32] = "ISO-8859-1";
529 static unsigned char adsifdn[4] = "\x00\x00\x00\x0F";
530 static unsigned char adsisec[4] = "\x9B\xDB\xF7\xAC";
531 static int adsiver = 1;
532 static char emaildateformat[32] = "%A, %B %d, %Y at %r";
535 static void populate_defaults(struct ast_vm_user *vmu)
537 ast_copy_flags(vmu, (&globalflags), AST_FLAGS_ALL);
538 if (saydurationminfo)
539 vmu->saydurationm = saydurationminfo;
541 ast_copy_string(vmu->callback, callcontext, sizeof(vmu->callback));
543 ast_copy_string(vmu->dialout, dialcontext, sizeof(vmu->dialout));
545 ast_copy_string(vmu->exit, exitcontext, sizeof(vmu->exit));
547 vmu->maxmsg = maxmsg;
548 vmu->volgain = volgain;
551 static void apply_option(struct ast_vm_user *vmu, const char *var, const char *value)
554 if (!strcasecmp(var, "attach")) {
555 ast_set2_flag(vmu, ast_true(value), VM_ATTACH);
556 } else if (!strcasecmp(var, "attachfmt")) {
557 ast_copy_string(vmu->attachfmt, value, sizeof(vmu->attachfmt));
558 } else if (!strcasecmp(var, "serveremail")) {
559 ast_copy_string(vmu->serveremail, value, sizeof(vmu->serveremail));
560 } else if (!strcasecmp(var, "language")) {
561 ast_copy_string(vmu->language, value, sizeof(vmu->language));
562 } else if (!strcasecmp(var, "tz")) {
563 ast_copy_string(vmu->zonetag, value, sizeof(vmu->zonetag));
565 } else if (!strcasecmp(var, "imapuser")) {
566 ast_copy_string(vmu->imapuser, value, sizeof(vmu->imapuser));
568 } else if (!strcasecmp(var, "delete") || !strcasecmp(var, "deletevoicemail")) {
569 ast_set2_flag(vmu, ast_true(value), VM_DELETE);
570 } else if (!strcasecmp(var, "saycid")){
571 ast_set2_flag(vmu, ast_true(value), VM_SAYCID);
572 } else if (!strcasecmp(var,"sendvoicemail")){
573 ast_set2_flag(vmu, ast_true(value), VM_SVMAIL);
574 } else if (!strcasecmp(var, "review")){
575 ast_set2_flag(vmu, ast_true(value), VM_REVIEW);
576 } else if (!strcasecmp(var, "tempgreetwarn")){
577 ast_set2_flag(vmu, ast_true(value), VM_TEMPGREETWARN);
578 } else if (!strcasecmp(var, "operator")){
579 ast_set2_flag(vmu, ast_true(value), VM_OPERATOR);
580 } else if (!strcasecmp(var, "envelope")){
581 ast_set2_flag(vmu, ast_true(value), VM_ENVELOPE);
582 } else if (!strcasecmp(var, "sayduration")){
583 ast_set2_flag(vmu, ast_true(value), VM_SAYDURATION);
584 } else if (!strcasecmp(var, "saydurationm")){
585 if (sscanf(value, "%d", &x) == 1) {
586 vmu->saydurationm = x;
588 ast_log(LOG_WARNING, "Invalid min duration for say duration\n");
590 } else if (!strcasecmp(var, "forcename")){
591 ast_set2_flag(vmu, ast_true(value), VM_FORCENAME);
592 } else if (!strcasecmp(var, "forcegreetings")){
593 ast_set2_flag(vmu, ast_true(value), VM_FORCEGREET);
594 } else if (!strcasecmp(var, "callback")) {
595 ast_copy_string(vmu->callback, value, sizeof(vmu->callback));
596 } else if (!strcasecmp(var, "dialout")) {
597 ast_copy_string(vmu->dialout, value, sizeof(vmu->dialout));
598 } else if (!strcasecmp(var, "exitcontext")) {
599 ast_copy_string(vmu->exit, value, sizeof(vmu->exit));
600 } else if (!strcasecmp(var, "maxmsg")) {
601 vmu->maxmsg = atoi(value);
602 if (vmu->maxmsg <= 0) {
603 ast_log(LOG_WARNING, "Invalid number of messages per folder maxmsg=%s. Using default value %i\n", value, MAXMSG);
604 vmu->maxmsg = MAXMSG;
605 } else if (vmu->maxmsg > MAXMSGLIMIT) {
606 ast_log(LOG_WARNING, "Maximum number of messages per folder is %i. Cannot accept value maxmsg=%s\n", MAXMSGLIMIT, value);
607 vmu->maxmsg = MAXMSGLIMIT;
609 } else if (!strcasecmp(var, "volgain")) {
610 sscanf(value, "%lf", &vmu->volgain);
611 } else if (!strcasecmp(var, "options")) {
612 apply_options(vmu, value);
616 static int change_password_realtime(struct ast_vm_user *vmu, const char *password)
619 if (!ast_strlen_zero(vmu->uniqueid)) {
620 res = ast_update_realtime("voicemail", "uniqueid", vmu->uniqueid, "password", password, NULL);
622 ast_copy_string(vmu->password, password, sizeof(vmu->password));
632 static void apply_options(struct ast_vm_user *vmu, const char *options)
633 { /* Destructively Parse options and apply */
637 stringp = ast_strdupa(options);
638 while ((s = strsep(&stringp, "|"))) {
640 if ((var = strsep(&value, "=")) && value) {
641 apply_option(vmu, var, value);
646 static void apply_options_full(struct ast_vm_user *retval, struct ast_variable *var)
648 struct ast_variable *tmp;
651 if (!strcasecmp(tmp->name, "password")) {
652 ast_copy_string(retval->password, tmp->value, sizeof(retval->password));
653 } else if (!strcasecmp(tmp->name, "uniqueid")) {
654 ast_copy_string(retval->uniqueid, tmp->value, sizeof(retval->uniqueid));
655 } else if (!strcasecmp(tmp->name, "pager")) {
656 ast_copy_string(retval->pager, tmp->value, sizeof(retval->pager));
657 } else if (!strcasecmp(tmp->name, "email")) {
658 ast_copy_string(retval->email, tmp->value, sizeof(retval->email));
659 } else if (!strcasecmp(tmp->name, "fullname")) {
660 ast_copy_string(retval->fullname, tmp->value, sizeof(retval->fullname));
661 } else if (!strcasecmp(tmp->name, "context")) {
662 ast_copy_string(retval->context, tmp->value, sizeof(retval->context));
664 apply_option(retval, tmp->name, tmp->value);
669 static struct ast_vm_user *find_user_realtime(struct ast_vm_user *ivm, const char *context, const char *mailbox)
671 struct ast_variable *var;
672 struct ast_vm_user *retval;
674 if ((retval = (ivm ? ivm : ast_calloc(1, sizeof(*retval))))) {
676 ast_set_flag(retval, VM_ALLOCED);
678 memset(retval, 0, sizeof(*retval));
680 ast_copy_string(retval->mailbox, mailbox, sizeof(retval->mailbox));
681 populate_defaults(retval);
682 if (!context && ast_test_flag((&globalflags), VM_SEARCH))
683 var = ast_load_realtime("voicemail", "mailbox", mailbox, NULL);
685 var = ast_load_realtime("voicemail", "mailbox", mailbox, "context", context, NULL);
687 apply_options_full(retval, var);
688 ast_variables_destroy(var);
698 static struct ast_vm_user *find_user(struct ast_vm_user *ivm, const char *context, const char *mailbox)
700 /* This function could be made to generate one from a database, too */
701 struct ast_vm_user *vmu=NULL, *cur;
702 AST_LIST_LOCK(&users);
704 if (!context && !ast_test_flag((&globalflags), VM_SEARCH))
707 AST_LIST_TRAVERSE(&users, cur, list) {
708 if (ast_test_flag((&globalflags), VM_SEARCH) && !strcasecmp(mailbox, cur->mailbox))
710 if (context && (!strcasecmp(context, cur->context)) && (!strcasecmp(mailbox, cur->mailbox)))
714 /* Make a copy, so that on a reload, we have no race */
715 if ((vmu = (ivm ? ivm : ast_malloc(sizeof(*vmu))))) {
716 memcpy(vmu, cur, sizeof(*vmu));
717 ast_set2_flag(vmu, !ivm, VM_ALLOCED);
718 AST_LIST_NEXT(vmu, list) = NULL;
721 vmu = find_user_realtime(ivm, context, mailbox);
722 AST_LIST_UNLOCK(&users);
726 static int reset_user_pw(const char *context, const char *mailbox, const char *newpass)
728 /* This function could be made to generate one from a database, too */
729 struct ast_vm_user *cur;
731 AST_LIST_LOCK(&users);
732 AST_LIST_TRAVERSE(&users, cur, list) {
733 if ((!context || !strcasecmp(context, cur->context)) &&
734 (!strcasecmp(mailbox, cur->mailbox)))
738 ast_copy_string(cur->password, newpass, sizeof(cur->password));
741 AST_LIST_UNLOCK(&users);
745 static void vm_change_password(struct ast_vm_user *vmu, const char *newpassword)
747 /* There's probably a better way of doing this. */
748 /* That's why I've put the password change in a separate function. */
749 /* This could also be done with a database function */
756 char currcontext[256] ="";
757 char tmpin[PATH_MAX];
758 char tmpout[PATH_MAX];
761 if (!change_password_realtime(vmu, newpassword))
764 snprintf(tmpin, sizeof(tmpin), "%s/voicemail.conf", ast_config_AST_CONFIG_DIR);
765 snprintf(tmpout, sizeof(tmpout), "%s/voicemail.conf.new", ast_config_AST_CONFIG_DIR);
766 configin = fopen(tmpin,"r");
768 configout = fopen(tmpout,"w+");
771 if (!configin || !configout) {
775 ast_log(LOG_WARNING, "Warning: Unable to open '%s' for reading: %s\n", tmpin, strerror(errno));
779 ast_log(LOG_WARNING, "Warning: Unable to open '%s' for writing: %s\n", tmpout, strerror(errno));
783 while (!feof(configin)) {
784 char *user = NULL, *pass = NULL, *rest = NULL, *comment = NULL, *tmpctx = NULL, *tmpctxend = NULL;
786 /* Read in the line */
787 if (fgets(inbuf, sizeof(inbuf), configin) == NULL)
791 /* Make a backup of it */
792 ast_copy_string(orig, inbuf, sizeof(orig));
795 Read the file line by line, split each line into a comment and command section
796 only parse the command portion of the line
798 if (inbuf[strlen(inbuf) - 1] == '\n')
799 inbuf[strlen(inbuf) - 1] = '\0';
801 if ((comment = strchr(inbuf, ';')))
802 *comment++ = '\0'; /* Now inbuf is terminated just before the comment */
804 if (ast_strlen_zero(inbuf)) {
805 fprintf(configout, "%s", orig);
809 /* Check for a context, first '[' to first ']' */
810 if ((tmpctx = strchr(inbuf, '['))) {
811 tmpctxend = strchr(tmpctx, ']');
814 ast_copy_string(currcontext, tmpctx + 1, tmpctxend - tmpctx);
815 fprintf(configout, "%s", orig);
820 /* This isn't a context line, check for MBX => PSWD... */
822 if ((pass = strchr(user, '='))) {
823 /* We have a line in the form of aaaaa=aaaaaa */
826 user = ast_strip(user);
831 pass = ast_skip_blanks(pass);
834 Since no whitespace allowed in fields, or more correctly white space
835 inside the fields is there for a purpose, we can just terminate pass
836 at the comma or EOL whichever comes first.
838 if ((rest = strchr(pass, ',')))
844 /* Compare user, pass AND context */
845 if (!ast_strlen_zero(user) && !strcmp(user, vmu->mailbox) &&
846 !ast_strlen_zero(pass) && !strcmp(pass, vmu->password) &&
847 !strcasecmp(currcontext, vmu->context)) {
848 /* This is the line */
850 fprintf(configout, "%s => %s,%s", user, newpassword, rest);
852 fprintf(configout, "%s => %s", user, newpassword);
854 /* If there was a comment on the line print it out */
856 fprintf(configout, ";%s\n", comment);
858 fprintf(configout, "\n");
861 /* Put it back like it was */
862 fprintf(configout, "%s", orig);
868 stat(tmpin, &statbuf);
869 chmod(tmpout, statbuf.st_mode);
870 chown(tmpout, statbuf.st_uid, statbuf.st_gid);
872 rename(tmpout, tmpin);
873 reset_user_pw(vmu->context, vmu->mailbox, newpassword);
874 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
877 static void vm_change_password_shell(struct ast_vm_user *vmu, char *newpassword)
880 snprintf(buf,255,"%s %s %s %s",ext_pass_cmd,vmu->context,vmu->mailbox,newpassword);
881 if (!ast_safe_system(buf))
882 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
885 static int make_dir(char *dest, int len, const char *context, const char *ext, const char *folder)
887 return snprintf(dest, len, "%s%s/%s/%s", VM_SPOOL_DIR, context, ext, folder);
891 static int make_gsm_file(char *dest, char *imapuser, char *dir, int num)
895 sprintf(gsmdir,"%s/%s",dir,imapuser);
896 if (mkdir(gsmdir, 01777) && (errno != EEXIST)) {
897 ast_log(LOG_WARNING, "mkdir '%s' failed: %s\n", gsmdir, strerror(errno));
898 return sprintf(dest, "%s/msg%04d", dir, num);
900 /* return sprintf(dest, "%s/s/msg%04d", dir, imapuser, num); */
901 return sprintf(dest, "%s/%s/msg%04d", dir, imapuser, num);
904 static void vm_imap_delete(int msgnum, struct vm_state *vms)
906 unsigned long messageNum = 0;
909 /* find real message number based on msgnum */
910 /* this may be an index into vms->msgArray based on the msgnum. */
912 messageNum = vms->msgArray[msgnum];
913 if (messageNum == 0) {
914 ast_log(LOG_WARNING, "msgnum %d, mailbox message %lu is zero.\n",msgnum,messageNum);
918 ast_log(LOG_DEBUG, "deleting msgnum %d, which is mailbox message %lu\n",msgnum,messageNum);
920 sprintf (arg,"%lu",messageNum);
921 mail_setflag (vms->mailstream,arg,"\\DELETED");
925 static int make_file(char *dest, int len, char *dir, int num)
927 return snprintf(dest, len, "%s/msg%04d", dir, num);
930 /*! \brief basically mkdir -p $dest/$context/$ext/$folder
931 * \param dest String. base directory.
932 * \param context String. Ignored if is null or empty string.
933 * \param ext String. Ignored if is null or empty string.
934 * \param folder String. Ignored if is null or empty string.
935 * \return 0 on failure, 1 on success.
937 static int create_dirpath(char *dest, int len, const char *context, const char *ext, const char *folder)
939 mode_t mode = VOICEMAIL_DIR_MODE;
941 if (!ast_strlen_zero(context)) {
942 make_dir(dest, len, context, "", "");
943 if (mkdir(dest, mode) && errno != EEXIST) {
944 ast_log(LOG_WARNING, "mkdir '%s' failed: %s\n", dest, strerror(errno));
948 if (!ast_strlen_zero(ext)) {
949 make_dir(dest, len, context, ext, "");
950 if (mkdir(dest, mode) && errno != EEXIST) {
951 ast_log(LOG_WARNING, "mkdir '%s' failed: %s\n", dest, strerror(errno));
955 if (!ast_strlen_zero(folder)) {
956 make_dir(dest, len, context, ext, folder);
957 if (mkdir(dest, mode) && errno != EEXIST) {
958 ast_log(LOG_WARNING, "mkdir '%s' failed: %s\n", dest, strerror(errno));
965 /* only return failure if ast_lock_path returns 'timeout',
966 not if the path does not exist or any other reason
968 static int vm_lock_path(const char *path)
970 switch (ast_lock_path(path)) {
971 case AST_LOCK_TIMEOUT:
980 static int retrieve_file(char *dir, int msgnum)
987 SQLSMALLINT colcount=0;
994 SQLSMALLINT datatype;
995 SQLSMALLINT decimaldigits;
996 SQLSMALLINT nullable;
1004 struct odbc_obj *obj;
1005 obj = odbc_request_obj(odbc_database, 0);
1007 ast_copy_string(fmt, vmfmts, sizeof(fmt));
1008 c = strchr(fmt, '|');
1011 if (!strcasecmp(fmt, "wav49"))
1013 snprintf(msgnums, sizeof(msgnums),"%d", msgnum);
1015 make_file(fn, sizeof(fn), dir, msgnum);
1017 ast_copy_string(fn, dir, sizeof(fn));
1018 snprintf(full_fn, sizeof(full_fn), "%s.txt", fn);
1019 f = fopen(full_fn, "w+");
1020 snprintf(full_fn, sizeof(full_fn), "%s.%s", fn, fmt);
1021 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
1022 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1023 ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
1024 odbc_release_obj(obj);
1027 snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE dir=? AND msgnum=?",odbc_table);
1028 res = SQLPrepare(stmt, sql, SQL_NTS);
1029 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1030 ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql);
1031 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1032 odbc_release_obj(obj);
1035 SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(dir), 0, (void *)dir, 0, NULL);
1036 SQLBindParameter(stmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(msgnums), 0, (void *)msgnums, 0, NULL);
1037 res = odbc_smart_execute(obj, stmt);
1038 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1039 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
1040 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1041 odbc_release_obj(obj);
1044 res = SQLFetch(stmt);
1045 if (res == SQL_NO_DATA) {
1046 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1047 odbc_release_obj(obj);
1050 else if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1051 ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
1052 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1053 odbc_release_obj(obj);
1056 fd = open(full_fn, O_RDWR | O_CREAT | O_TRUNC, 0770);
1058 ast_log(LOG_WARNING, "Failed to write '%s': %s\n", full_fn, strerror(errno));
1059 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1060 odbc_release_obj(obj);
1063 res = SQLNumResultCols(stmt, &colcount);
1064 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1065 ast_log(LOG_WARNING, "SQL Column Count error!\n[%s]\n\n", sql);
1066 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1067 odbc_release_obj(obj);
1071 fprintf(f, "[message]\n");
1072 for (x=0;x<colcount;x++) {
1074 collen = sizeof(coltitle);
1075 res = SQLDescribeCol(stmt, x + 1, coltitle, sizeof(coltitle), &collen,
1076 &datatype, &colsize, &decimaldigits, &nullable);
1077 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1078 ast_log(LOG_WARNING, "SQL Describe Column error!\n[%s]\n\n", sql);
1079 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1080 odbc_release_obj(obj);
1083 if (!strcasecmp(coltitle, "recording")) {
1084 res = SQLGetData(stmt, x + 1, SQL_BINARY, NULL, 0, &colsize);
1088 lseek(fd, fdlen - 1, SEEK_SET);
1089 if (write(fd, tmp, 1) != 1) {
1095 fdm = mmap(NULL, fdlen, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
1098 memset(fdm, 0, fdlen);
1099 res = SQLGetData(stmt, x + 1, SQL_BINARY, fdm, fdlen, &colsize);
1100 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1101 ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
1102 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1103 odbc_release_obj(obj);
1108 res = SQLGetData(stmt, x + 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
1109 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1110 ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
1111 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1112 odbc_release_obj(obj);
1115 if (strcasecmp(coltitle, "msgnum") && strcasecmp(coltitle, "dir") && f)
1116 fprintf(f, "%s=%s\n", coltitle, rowdata);
1119 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1120 odbc_release_obj(obj);
1122 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
1133 static int remove_file(char *dir, int msgnum)
1140 snprintf(msgnums, sizeof(msgnums), "%d", msgnum);
1141 make_file(fn, sizeof(fn), dir, msgnum);
1143 ast_copy_string(fn, dir, sizeof(fn));
1144 ast_filedelete(fn, NULL);
1145 snprintf(full_fn, sizeof(full_fn), "%s.txt", fn);
1150 static int last_message_index(struct ast_vm_user *vmu, char *dir)
1158 struct odbc_obj *obj;
1159 obj = odbc_request_obj(odbc_database, 0);
1161 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
1162 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1163 ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
1164 odbc_release_obj(obj);
1167 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir=?",odbc_table);
1168 res = SQLPrepare(stmt, sql, SQL_NTS);
1169 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1170 ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql);
1171 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1172 odbc_release_obj(obj);
1175 SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(dir), 0, (void *)dir, 0, NULL);
1176 res = odbc_smart_execute(obj, stmt);
1177 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1178 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
1179 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1180 odbc_release_obj(obj);
1183 res = SQLFetch(stmt);
1184 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1185 ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
1186 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1187 odbc_release_obj(obj);
1190 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
1191 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1192 ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
1193 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1194 odbc_release_obj(obj);
1197 if (sscanf(rowdata, "%d", &x) != 1)
1198 ast_log(LOG_WARNING, "Failed to read message count!\n");
1199 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1200 odbc_release_obj(obj);
1202 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
1207 static int message_exists(char *dir, int msgnum)
1216 struct odbc_obj *obj;
1217 obj = odbc_request_obj(odbc_database, 0);
1219 snprintf(msgnums, sizeof(msgnums), "%d", msgnum);
1220 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
1221 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1222 ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
1223 odbc_release_obj(obj);
1226 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir=? AND msgnum=?",odbc_table);
1227 res = SQLPrepare(stmt, sql, SQL_NTS);
1228 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1229 ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql);
1230 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1231 odbc_release_obj(obj);
1234 SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(dir), 0, (void *)dir, 0, NULL);
1235 SQLBindParameter(stmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(msgnums), 0, (void *)msgnums, 0, NULL);
1236 res = odbc_smart_execute(obj, stmt);
1237 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1238 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
1239 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1240 odbc_release_obj(obj);
1243 res = SQLFetch(stmt);
1244 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1245 ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
1246 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1247 odbc_release_obj(obj);
1250 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
1251 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1252 ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
1253 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1254 odbc_release_obj(obj);
1257 if (sscanf(rowdata, "%d", &x) != 1)
1258 ast_log(LOG_WARNING, "Failed to read message count!\n");
1259 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1260 odbc_release_obj(obj);
1262 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
1267 static int count_messages(struct ast_vm_user *vmu, char *dir)
1269 return last_message_index(vmu, dir) + 1;
1272 static void delete_file(char *sdir, int smsg)
1279 struct odbc_obj *obj;
1280 obj = odbc_request_obj(odbc_database, 0);
1282 snprintf(msgnums, sizeof(msgnums), "%d", smsg);
1283 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
1284 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1285 ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
1286 odbc_release_obj(obj);
1289 snprintf(sql, sizeof(sql), "DELETE FROM %s WHERE dir=? AND msgnum=?",odbc_table);
1290 res = SQLPrepare(stmt, sql, SQL_NTS);
1291 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1292 ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql);
1293 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1294 odbc_release_obj(obj);
1297 SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(sdir), 0, (void *)sdir, 0, NULL);
1298 SQLBindParameter(stmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(msgnums), 0, (void *)msgnums, 0, NULL);
1299 res = odbc_smart_execute(obj, stmt);
1300 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1301 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
1302 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1303 odbc_release_obj(obj);
1306 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1307 odbc_release_obj(obj);
1309 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
1314 static void copy_file(char *sdir, int smsg, char *ddir, int dmsg, char *dmailboxuser, char *dmailboxcontext)
1321 struct odbc_obj *obj;
1323 delete_file(ddir, dmsg);
1324 obj = odbc_request_obj(odbc_database, 0);
1326 snprintf(msgnums, sizeof(msgnums), "%d", smsg);
1327 snprintf(msgnumd, sizeof(msgnumd), "%d", dmsg);
1328 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
1329 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1330 ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
1331 odbc_release_obj(obj);
1334 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);
1335 res = SQLPrepare(stmt, sql, SQL_NTS);
1336 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1337 ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql);
1338 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1339 odbc_release_obj(obj);
1342 SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(ddir), 0, (void *)ddir, 0, NULL);
1343 SQLBindParameter(stmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(msgnumd), 0, (void *)msgnumd, 0, NULL);
1344 SQLBindParameter(stmt, 3, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(dmailboxuser), 0, (void *)dmailboxuser, 0, NULL);
1345 SQLBindParameter(stmt, 4, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(dmailboxcontext), 0, (void *)dmailboxcontext, 0, NULL);
1346 SQLBindParameter(stmt, 5, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(sdir), 0, (void *)sdir, 0, NULL);
1347 SQLBindParameter(stmt, 6, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(msgnums), 0, (void *)msgnums, 0, NULL);
1348 res = odbc_smart_execute(obj, stmt);
1349 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1350 ast_log(LOG_WARNING, "SQL Execute error!\n[%s] (You probably don't have MySQL 4.1 or later installed)\n\n", sql);
1351 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1352 odbc_release_obj(obj);
1355 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1356 odbc_release_obj(obj);
1358 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
1363 static int store_file(char *dir, char *mailboxuser, char *mailboxcontext, int msgnum)
1378 char *context="", *macrocontext="", *callerid="", *origtime="", *duration="";
1379 char *category = "";
1380 struct ast_config *cfg=NULL;
1381 struct odbc_obj *obj;
1383 delete_file(dir, msgnum);
1384 obj = odbc_request_obj(odbc_database, 0);
1386 ast_copy_string(fmt, vmfmts, sizeof(fmt));
1387 c = strchr(fmt, '|');
1390 if (!strcasecmp(fmt, "wav49"))
1392 snprintf(msgnums, sizeof(msgnums),"%d", msgnum);
1394 make_file(fn, sizeof(fn), dir, msgnum);
1396 ast_copy_string(fn, dir, sizeof(fn));
1397 snprintf(full_fn, sizeof(full_fn), "%s.txt", fn);
1398 cfg = ast_config_load(full_fn);
1399 snprintf(full_fn, sizeof(full_fn), "%s.%s", fn, fmt);
1400 fd = open(full_fn, O_RDWR);
1402 ast_log(LOG_WARNING, "Open of sound file '%s' failed: %s\n", full_fn, strerror(errno));
1403 odbc_release_obj(obj);
1407 context = ast_variable_retrieve(cfg, "message", "context");
1408 if (!context) context = "";
1409 macrocontext = ast_variable_retrieve(cfg, "message", "macrocontext");
1410 if (!macrocontext) macrocontext = "";
1411 callerid = ast_variable_retrieve(cfg, "message", "callerid");
1412 if (!callerid) callerid = "";
1413 origtime = ast_variable_retrieve(cfg, "message", "origtime");
1414 if (!origtime) origtime = "";
1415 duration = ast_variable_retrieve(cfg, "message", "duration");
1416 if (!duration) duration = "";
1417 category = ast_variable_retrieve(cfg, "message", "category");
1418 if (!category) category = "";
1420 fdlen = lseek(fd, 0, SEEK_END);
1421 lseek(fd, 0, SEEK_SET);
1422 printf("Length is %zd\n", fdlen);
1423 fdm = mmap(NULL, fdlen, PROT_READ | PROT_WRITE, MAP_SHARED,fd, 0);
1425 ast_log(LOG_WARNING, "Memory map failed!\n");
1426 odbc_release_obj(obj);
1429 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
1430 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1431 ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
1432 odbc_release_obj(obj);
1435 if (!ast_strlen_zero(category))
1436 snprintf(sql, sizeof(sql), "INSERT INTO %s (dir,msgnum,recording,context,macrocontext,callerid,origtime,duration,mailboxuser,mailboxcontext,category) VALUES (?,?,?,?,?,?,?,?,?,?,?)",odbc_table);
1438 snprintf(sql, sizeof(sql), "INSERT INTO %s (dir,msgnum,recording,context,macrocontext,callerid,origtime,duration,mailboxuser,mailboxcontext) VALUES (?,?,?,?,?,?,?,?,?,?)",odbc_table);
1439 res = SQLPrepare(stmt, sql, SQL_NTS);
1440 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1441 ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql);
1442 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1443 odbc_release_obj(obj);
1446 len = fdlen; /* SQL_LEN_DATA_AT_EXEC(fdlen); */
1447 SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(dir), 0, (void *)dir, 0, NULL);
1448 SQLBindParameter(stmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(msgnums), 0, (void *)msgnums, 0, NULL);
1449 SQLBindParameter(stmt, 3, SQL_PARAM_INPUT, SQL_C_BINARY, SQL_LONGVARBINARY, fdlen, 0, (void *)fdm, fdlen, &len);
1450 SQLBindParameter(stmt, 4, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(context), 0, (void *)context, 0, NULL);
1451 SQLBindParameter(stmt, 5, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(macrocontext), 0, (void *)macrocontext, 0, NULL);
1452 SQLBindParameter(stmt, 6, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(callerid), 0, (void *)callerid, 0, NULL);
1453 SQLBindParameter(stmt, 7, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(origtime), 0, (void *)origtime, 0, NULL);
1454 SQLBindParameter(stmt, 8, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(duration), 0, (void *)duration, 0, NULL);
1455 SQLBindParameter(stmt, 9, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(mailboxuser), 0, (void *)mailboxuser, 0, NULL);
1456 SQLBindParameter(stmt, 10, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(mailboxcontext), 0, (void *)mailboxcontext, 0, NULL);
1457 if (!ast_strlen_zero(category))
1458 SQLBindParameter(stmt, 11, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(category), 0, (void *)category, 0, NULL);
1459 res = odbc_smart_execute(obj, stmt);
1460 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1461 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
1462 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1463 odbc_release_obj(obj);
1466 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1467 odbc_release_obj(obj);
1469 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
1472 ast_config_destroy(cfg);
1480 static void rename_file(char *sdir, int smsg, char *mailboxuser, char *mailboxcontext, char *ddir, int dmsg)
1487 struct odbc_obj *obj;
1489 delete_file(ddir, dmsg);
1490 obj = odbc_request_obj(odbc_database, 0);
1492 snprintf(msgnums, sizeof(msgnums), "%d", smsg);
1493 snprintf(msgnumd, sizeof(msgnumd), "%d", dmsg);
1494 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
1495 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1496 ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
1497 odbc_release_obj(obj);
1500 snprintf(sql, sizeof(sql), "UPDATE %s SET dir=?, msgnum=?, mailboxuser=?, mailboxcontext=? WHERE dir=? AND msgnum=?",odbc_table);
1501 res = SQLPrepare(stmt, sql, SQL_NTS);
1502 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1503 ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql);
1504 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1505 odbc_release_obj(obj);
1508 SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(ddir), 0, (void *)ddir, 0, NULL);
1509 SQLBindParameter(stmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(msgnumd), 0, (void *)msgnumd, 0, NULL);
1510 SQLBindParameter(stmt, 3, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(mailboxuser), 0, (void *)mailboxuser, 0, NULL);
1511 SQLBindParameter(stmt, 4, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(mailboxcontext), 0, (void *)mailboxcontext, 0, NULL);
1512 SQLBindParameter(stmt, 5, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(sdir), 0, (void *)sdir, 0, NULL);
1513 SQLBindParameter(stmt, 6, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(msgnums), 0, (void *)msgnums, 0, NULL);
1514 res = odbc_smart_execute(obj, stmt);
1515 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
1516 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
1517 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1518 odbc_release_obj(obj);
1521 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
1522 odbc_release_obj(obj);
1524 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
1530 static int count_messages(struct ast_vm_user *vmu, char *dir)
1532 /* Find all .txt files - even if they are not in sequence from 0000 */
1536 struct dirent *vment = NULL;
1538 if (vm_lock_path(dir))
1539 return ERROR_LOCK_PATH;
1541 if ((vmdir = opendir(dir))) {
1542 while ((vment = readdir(vmdir))) {
1543 if (strlen(vment->d_name) > 7 && !strncmp(vment->d_name + 7, ".txt", 4))
1548 ast_unlock_path(dir);
1553 static void rename_file(char *sfn, char *dfn)
1557 ast_filerename(sfn,dfn,NULL);
1558 snprintf(stxt, sizeof(stxt), "%s.txt", sfn);
1559 snprintf(dtxt, sizeof(dtxt), "%s.txt", dfn);
1563 static int copy(char *infile, char *outfile)
1571 #ifdef HARDLINK_WHEN_POSSIBLE
1572 /* Hard link if possible; saves disk space & is faster */
1573 if (link(infile, outfile)) {
1575 if ((ifd = open(infile, O_RDONLY)) < 0) {
1576 ast_log(LOG_WARNING, "Unable to open %s in read-only mode\n", infile);
1579 if ((ofd = open(outfile, O_WRONLY | O_TRUNC | O_CREAT, VOICEMAIL_FILE_MODE)) < 0) {
1580 ast_log(LOG_WARNING, "Unable to open %s in write-only mode\n", outfile);
1585 len = read(ifd, buf, sizeof(buf));
1587 ast_log(LOG_WARNING, "Read failed on %s: %s\n", infile, strerror(errno));
1593 res = write(ofd, buf, len);
1594 if (errno == ENOMEM || errno == ENOSPC || res != len) {
1595 ast_log(LOG_WARNING, "Write failed on %s (%d of %d): %s\n", outfile, res, len, strerror(errno));
1605 #ifdef HARDLINK_WHEN_POSSIBLE
1607 /* Hard link succeeded */
1613 static void copy_file(char *frompath, char *topath)
1615 char frompath2[256],topath2[256];
1616 ast_filecopy(frompath, topath, NULL);
1617 snprintf(frompath2, sizeof(frompath2), "%s.txt", frompath);
1618 snprintf(topath2, sizeof(topath2), "%s.txt", topath);
1619 copy(frompath2, topath2);
1623 * A negative return value indicates an error.
1625 static int last_message_index(struct ast_vm_user *vmu, char *dir)
1630 if (vm_lock_path(dir))
1631 return ERROR_LOCK_PATH;
1633 for (x = 0; x < vmu->maxmsg; x++) {
1634 make_file(fn, sizeof(fn), dir, x);
1635 if (ast_fileexists(fn, NULL, NULL) < 1)
1638 ast_unlock_path(dir);
1643 static int vm_delete(char *file)
1648 txtsize = (strlen(file) + 5)*sizeof(char);
1649 txt = alloca(txtsize);
1650 /* Sprintf here would safe because we alloca'd exactly the right length,
1651 * but trying to eliminate all sprintf's anyhow
1653 snprintf(txt, txtsize, "%s.txt", file);
1655 return ast_filedelete(file, NULL);
1660 static int inbuf(struct baseio *bio, FILE *fi)
1667 if ((l = fread(bio->iobuf,1,BASEMAXINLINE,fi)) <= 0) {
1681 static int inchar(struct baseio *bio, FILE *fi)
1683 if (bio->iocp>=bio->iolen) {
1684 if (!inbuf(bio, fi))
1688 return bio->iobuf[bio->iocp++];
1691 static int ochar(struct baseio *bio, int c, FILE *so)
1693 if (bio->linelength>=BASELINELEN) {
1694 if (fputs(eol,so)==EOF)
1700 if (putc(((unsigned char)c),so)==EOF)
1708 static int base_encode(char *filename, FILE *so)
1710 unsigned char dtable[BASEMAXINLINE];
1715 memset(&bio, 0, sizeof(bio));
1716 bio.iocp = BASEMAXINLINE;
1718 if (!(fi = fopen(filename, "rb"))) {
1719 ast_log(LOG_WARNING, "Failed to open log file: %s: %s\n", filename, strerror(errno));
1723 for (i= 0;i<9;i++) {
1726 dtable[26+i]= 'a'+i;
1727 dtable[26+i+9]= 'j'+i;
1729 for (i= 0;i<8;i++) {
1730 dtable[i+18]= 'S'+i;
1731 dtable[26+i+18]= 's'+i;
1733 for (i= 0;i<10;i++) {
1734 dtable[52+i]= '0'+i;
1740 unsigned char igroup[3],ogroup[4];
1743 igroup[0]= igroup[1]= igroup[2]= 0;
1745 for (n= 0;n<3;n++) {
1746 if ((c = inchar(&bio, fi)) == EOF) {
1751 igroup[n]= (unsigned char)c;
1755 ogroup[0]= dtable[igroup[0]>>2];
1756 ogroup[1]= dtable[((igroup[0]&3)<<4)|(igroup[1]>>4)];
1757 ogroup[2]= dtable[((igroup[1]&0xF)<<2)|(igroup[2]>>6)];
1758 ogroup[3]= dtable[igroup[2]&0x3F];
1768 ochar(&bio, ogroup[i], so);
1772 if (fputs(eol,so)==EOF)
1780 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)
1783 /* Prepare variables for substition in email body and subject */
1784 pbx_builtin_setvar_helper(ast, "VM_NAME", vmu->fullname);
1785 pbx_builtin_setvar_helper(ast, "VM_DUR", dur);
1786 snprintf(passdata, passdatasize, "%d", msgnum);
1787 pbx_builtin_setvar_helper(ast, "VM_MSGNUM", passdata);
1788 pbx_builtin_setvar_helper(ast, "VM_CONTEXT", context);
1789 pbx_builtin_setvar_helper(ast, "VM_MAILBOX", mailbox);
1790 pbx_builtin_setvar_helper(ast, "VM_CALLERID", ast_callerid_merge(callerid, sizeof(callerid), cidname, cidnum, "Unknown Caller"));
1791 pbx_builtin_setvar_helper(ast, "VM_CIDNAME", (cidname ? cidname : "an unknown caller"));
1792 pbx_builtin_setvar_helper(ast, "VM_CIDNUM", (cidnum ? cidnum : "an unknown caller"));
1793 pbx_builtin_setvar_helper(ast, "VM_DATE", date);
1794 pbx_builtin_setvar_helper(ast, "VM_CATEGORY", category ? ast_strdupa(category) : "no category");
1798 * fill in *tm for current time according to the proper timezone, if any.
1799 * Return tm so it can be used as a function argument.
1801 static const struct tm *vmu_tm(const struct ast_vm_user *vmu, struct tm *tm)
1803 const struct vm_zone *z = NULL;
1804 time_t t = time(NULL);
1806 /* Does this user have a timezone specified? */
1807 if (!ast_strlen_zero(vmu->zonetag)) {
1808 /* Find the zone in the list */
1809 AST_LIST_LOCK(&zones);
1810 AST_LIST_TRAVERSE(&zones, z, list) {
1811 if (!strcmp(z->name, vmu->zonetag))
1814 AST_LIST_UNLOCK(&zones);
1816 ast_localtime(&t, tm, z ? z->timezone : NULL);
1820 /* same as mkstemp, but return a FILE * */
1821 static FILE *vm_mkftemp(char *template)
1824 int pfd = mkstemp(template);
1826 p = fdopen(pfd, "w");
1835 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)
1839 char host[MAXHOSTNAMELEN] = "";
1844 char tmp[80] = "/tmp/astmail-XXXXXX";
1849 if (vmu && ast_strlen_zero(vmu->email)) {
1850 ast_log(LOG_WARNING, "E-mail address missing for mailbox [%s]. E-mail will not be sent.\n", vmu->mailbox);
1853 if (!strcmp(format, "wav49"))
1855 ast_log(LOG_DEBUG, "Attaching file '%s', format '%s', uservm is '%d', global is %d\n", attach, format, attach_user_voicemail, ast_test_flag((&globalflags), VM_ATTACH));
1856 /* Make a temporary file instead of piping directly to sendmail, in case the mail
1858 if ((p = vm_mkftemp(tmp)) == NULL) {
1859 ast_log(LOG_WARNING, "Unable to launch '%s' (can't create temporary file)\n", mailcmd);
1862 gethostname(host, sizeof(host)-1);
1863 if (strchr(srcemail, '@'))
1864 ast_copy_string(who, srcemail, sizeof(who));
1866 snprintf(who, sizeof(who), "%s@%s", srcemail, host);
1868 snprintf(dur, sizeof(dur), "%d:%02d", duration / 60, duration % 60);
1869 strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", vmu_tm(vmu, &tm));
1870 fprintf(p, "Date: %s\n", date);
1872 /* Set date format for voicemail mail */
1873 strftime(date, sizeof(date), emaildateformat, &tm);
1876 struct ast_channel *ast;
1877 if ((ast = ast_channel_alloc(0))) {
1879 int vmlen = strlen(fromstring)*3 + 200;
1880 if ((passdata = alloca(vmlen))) {
1881 memset(passdata, 0, vmlen);
1882 prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category);
1883 pbx_substitute_variables_helper(ast, fromstring, passdata, vmlen);
1884 fprintf(p, "From: %s <%s>\n",passdata,who);
1886 ast_log(LOG_WARNING, "Cannot allocate workspace for variable substitution\n");
1887 ast_channel_free(ast);
1889 ast_log(LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
1891 fprintf(p, "From: Asterisk PBX <%s>\n", who);
1892 fprintf(p, "To: %s <%s>\n", vmu->fullname, vmu->email);
1895 struct ast_channel *ast;
1896 if ((ast = ast_channel_alloc(0))) {
1898 int vmlen = strlen(emailsubject)*3 + 200;
1899 if ((passdata = alloca(vmlen))) {
1900 memset(passdata, 0, vmlen);
1901 prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category);
1902 pbx_substitute_variables_helper(ast, emailsubject, passdata, vmlen);
1903 fprintf(p, "Subject: %s\n", passdata);
1905 ast_log(LOG_WARNING, "Cannot allocate workspace for variable substitution\n");
1906 ast_channel_free(ast);
1908 ast_log(LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
1911 fprintf(p, emailtitle, msgnum + 1, mailbox) ;
1913 } else if (ast_test_flag((&globalflags), VM_PBXSKIP))
1914 fprintf(p, "Subject: New message %d in mailbox %s\n", msgnum + 1, mailbox);
1916 fprintf(p, "Subject: [PBX]: New message %d in mailbox %s\n", msgnum + 1, mailbox);
1917 fprintf(p, "Message-ID: <Asterisk-%d-%d-%s-%d@%s>\n", msgnum, (unsigned int)ast_random(), mailbox, getpid(), host);
1919 /* additional information needed for IMAP searching */
1920 fprintf(p, "X-Asterisk-VM-Message-Num: %d\n", msgnum + 1);
1921 /* fprintf(p, "X-Asterisk-VM-Orig-Mailbox: %s\n", ext); */
1922 fprintf(p, "X-Asterisk-VM-Server-Name: %s\n", fromstring);
1923 fprintf(p, "X-Asterisk-VM-Context: %s\n", context);
1924 fprintf(p, "X-Asterisk-VM-Extension: %s\n", chan->exten);
1925 fprintf(p, "X-Asterisk-VM-Priority: %d\n", chan->priority);
1926 fprintf(p, "X-Asterisk-VM-Caller-channel: %s\n", chan->name);
1927 fprintf(p, "X-Asterisk-VM-Caller-ID-Num: %s\n", cidnum);
1928 fprintf(p, "X-Asterisk-VM-Caller-ID-Name: %s\n", cidname);
1929 fprintf(p, "X-Asterisk-VM-Duration: %d\n", duration);
1930 if (!ast_strlen_zero(category))
1931 fprintf(p, "X-Asterisk-VM-Category: %s\n", category);
1932 fprintf(p, "X-Asterisk-VM-Orig-date: %s\n", date);
1933 fprintf(p, "X-Asterisk-VM-Orig-time: %ld\n", (long)time(NULL));
1935 if (!ast_strlen_zero(cidnum))
1936 fprintf(p, "X-Asterisk-CallerID: %s\n", cidnum);
1937 if (!ast_strlen_zero(cidname))
1938 fprintf(p, "X-Asterisk-CallerIDName: %s\n", cidname);
1939 fprintf(p, "MIME-Version: 1.0\n");
1940 if (attach_user_voicemail) {
1941 /* Something unique. */
1942 snprintf(bound, sizeof(bound), "voicemail_%d%s%d%d", msgnum, mailbox, getpid(), (unsigned int)ast_random());
1944 fprintf(p, "Content-Type: multipart/mixed; boundary=\"%s\"\n\n\n", bound);
1946 fprintf(p, "--%s\n", bound);
1948 fprintf(p, "Content-Type: text/plain; charset=%s\nContent-Transfer-Encoding: 8bit\n\n", charset);
1950 struct ast_channel *ast;
1951 if ((ast = ast_channel_alloc(0))) {
1953 int vmlen = strlen(emailbody)*3 + 200;
1954 if ((passdata = alloca(vmlen))) {
1955 memset(passdata, 0, vmlen);
1956 prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category);
1957 pbx_substitute_variables_helper(ast, emailbody, passdata, vmlen);
1958 fprintf(p, "%s\n", passdata);
1960 ast_log(LOG_WARNING, "Cannot allocate workspace for variable substitution\n");
1961 ast_channel_free(ast);
1963 ast_log(LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
1965 fprintf(p, "Dear %s:\n\n\tJust wanted to let you know you were just left a %s long message (number %d)\n"
1967 "in mailbox %s from %s, on %s so you might\n"
1968 "want to check it when you get a chance. Thanks!\n\n\t\t\t\t--Asterisk\n\n", vmu->fullname,
1969 dur, msgnum + 1, mailbox, (cidname ? cidname : (cidnum ? cidnum : "an unknown caller")), date);
1971 if (attach_user_voicemail) {
1972 /* Eww. We want formats to tell us their own MIME type */
1973 char *ctype = (!strcasecmp(format, "ogg")) ? "application/" : "audio/x-";
1974 char tmpdir[256], newtmp[256];
1977 create_dirpath(tmpdir, sizeof(tmpdir), vmu->context, vmu->mailbox, "tmp");
1978 snprintf(newtmp, sizeof(newtmp), "%s/XXXXXX", tmpdir);
1979 tmpfd = mkstemp(newtmp);
1980 ast_log(LOG_DEBUG, "newtmp: %s\n", newtmp);
1981 if (vmu->volgain < -.001 || vmu->volgain > .001) {
1982 snprintf(tmpcmd, sizeof(tmpcmd), "sox -v %.4f %s.%s %s.%s", vmu->volgain, attach, format, newtmp, format);
1983 ast_safe_system(tmpcmd);
1985 ast_log(LOG_DEBUG, "VOLGAIN: Stored at: %s.%s - Level: %.4f - Mailbox: %s\n", attach, format, vmu->volgain, mailbox);
1987 fprintf(p, "--%s\n", bound);
1988 fprintf(p, "Content-Type: %s%s; name=\"msg%04d.%s\"\n", ctype, format, msgnum, format);
1989 fprintf(p, "Content-Transfer-Encoding: base64\n");
1990 fprintf(p, "Content-Description: Voicemail sound attachment.\n");
1991 fprintf(p, "Content-Disposition: attachment; filename=\"msg%04d.%s\"\n\n", msgnum, format);
1993 snprintf(fname, sizeof(fname), "%s.%s", attach, format);
1994 base_encode(fname, p);
1996 /* only attach if necessary */
1997 if (strcmp(format, "gsm")) {
1998 fprintf(p, "--%s\n", bound);
1999 fprintf(p, "Content-Type: audio/x-gsm; name=\"msg%04d.%s\"\n", msgnum, format);
2000 fprintf(p, "Content-Transfer-Encoding: base64\n");
2001 fprintf(p, "Content-Description: Voicemail sound attachment.\n");
2002 fprintf(p, "Content-Disposition: attachment; filename=\"msg%04d.gsm\"\n\n", msgnum);
2003 snprintf(fname, sizeof(fname), "%s.gsm", attach);
2004 base_encode(fname, p);
2007 fprintf(p, "\n\n--%s--\n.\n", bound);
2013 snprintf(tmp2, sizeof(tmp2), "( %s < %s ; rm -f %s ) &", mailcmd, tmp, tmp);
2014 ast_safe_system(tmp2);
2015 ast_log(LOG_DEBUG, "Sent mail to %s with command '%s'\n", vmu->email, mailcmd);
2020 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)
2023 char host[MAXHOSTNAMELEN]="";
2026 char tmp[80] = "/tmp/astmail-XXXXXX";
2031 if ((p = vm_mkftemp(tmp)) == NULL) {
2032 ast_log(LOG_WARNING, "Unable to launch '%s' (can't create temporary file)\n", mailcmd);
2035 gethostname(host, sizeof(host)-1);
2036 if (strchr(srcemail, '@'))
2037 ast_copy_string(who, srcemail, sizeof(who));
2039 snprintf(who, sizeof(who), "%s@%s", srcemail, host);
2041 snprintf(dur, sizeof(dur), "%d:%02d", duration / 60, duration % 60);
2042 strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", vmu_tm(vmu, &tm));
2043 fprintf(p, "Date: %s\n", date);
2045 if (*pagerfromstring) {
2046 struct ast_channel *ast;
2047 if ((ast = ast_channel_alloc(0))) {
2049 int vmlen = strlen(fromstring)*3 + 200;
2050 if ((passdata = alloca(vmlen))) {
2051 memset(passdata, 0, vmlen);
2052 prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category);
2053 pbx_substitute_variables_helper(ast, pagerfromstring, passdata, vmlen);
2054 fprintf(p, "From: %s <%s>\n", passdata, who);
2056 ast_log(LOG_WARNING, "Cannot allocate workspace for variable substitution\n");
2057 ast_channel_free(ast);
2058 } else ast_log(LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
2060 fprintf(p, "From: Asterisk PBX <%s>\n", who);
2061 fprintf(p, "To: %s\n", pager);
2063 struct ast_channel *ast;
2064 if ((ast = ast_channel_alloc(0))) {
2066 int vmlen = strlen(pagersubject) * 3 + 200;
2067 if ((passdata = alloca(vmlen))) {
2068 memset(passdata, 0, vmlen);
2069 prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category);
2070 pbx_substitute_variables_helper(ast, pagersubject, passdata, vmlen);
2071 fprintf(p, "Subject: %s\n\n", passdata);
2072 } else ast_log(LOG_WARNING, "Cannot allocate workspace for variable substitution\n");
2073 ast_channel_free(ast);
2074 } else ast_log(LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
2076 fprintf(p, "Subject: New VM\n\n");
2077 strftime(date, sizeof(date), "%A, %B %d, %Y at %r", &tm);
2079 struct ast_channel *ast;
2080 if ((ast = ast_channel_alloc(0))) {
2082 int vmlen = strlen(pagerbody)*3 + 200;
2083 if ((passdata = alloca(vmlen))) {
2084 memset(passdata, 0, vmlen);
2085 prep_email_sub_vars(ast, vmu, msgnum + 1, context, mailbox, cidnum, cidname, dur, date, passdata, vmlen, category);
2086 pbx_substitute_variables_helper(ast, pagerbody, passdata, vmlen);
2087 fprintf(p, "%s\n", passdata);
2088 } else ast_log(LOG_WARNING, "Cannot allocate workspace for variable substitution\n");
2089 ast_channel_free(ast);
2090 } else ast_log(LOG_WARNING, "Cannot allocate the channel for variables substitution\n");
2092 fprintf(p, "New %s long msg in box %s\n"
2093 "from %s, on %s", dur, mailbox, (cidname ? cidname : (cidnum ? cidnum : "unknown")), date);
2096 snprintf(tmp2, sizeof(tmp2), "( %s < %s ; rm -f %s ) &", mailcmd, tmp, tmp);
2097 ast_safe_system(tmp2);
2098 ast_log(LOG_DEBUG, "Sent page to %s with command '%s'\n", pager, mailcmd);
2103 #ifndef IMAP_STORAGE
2104 static int get_date(char *s, int len)
2109 localtime_r(&t,&tm);
2110 return strftime(s, len, "%a %b %e %r %Z %Y", &tm);
2114 static int invent_message(struct ast_channel *chan, char *context, char *ext, int busy, char *ecodes)
2118 snprintf(fn, sizeof(fn), "%s%s/%s/greet", VM_SPOOL_DIR, context, ext);
2120 if (ast_fileexists(fn, NULL, NULL) > 0) {
2121 res = ast_stream_and_wait(chan, fn, chan->language, ecodes);
2127 /* Dispose just in case */
2129 res = ast_stream_and_wait(chan, "vm-theperson", chan->language, ecodes);
2132 res = ast_say_digit_str(chan, ext, ecodes, chan->language);
2136 res = ast_stream_and_wait(chan, busy ? "vm-isonphone" : "vm-isunavail", chan->language, ecodes);
2140 static void free_user(struct ast_vm_user *vmu)
2142 if (ast_test_flag(vmu, VM_ALLOCED))
2146 static void free_zone(struct vm_zone *z)
2151 static const char *mbox(int id)
2153 static const char *msgs[] = {
2165 return (id >= 0 && id < (sizeof(msgs)/sizeof(msgs[0]))) ? msgs[id] : "Unknown";
2169 static int inboxcount(const char *mailbox, int *newmsgs, int *oldmsgs)
2177 struct odbc_obj *obj;
2185 /* If no mailbox, return immediately */
2186 if (ast_strlen_zero(mailbox))
2189 ast_copy_string(tmp, mailbox, sizeof(tmp));
2191 context = strchr(tmp, '@');
2196 context = "default";
2198 obj = odbc_request_obj(odbc_database, 0);
2200 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
2201 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2202 ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
2203 odbc_release_obj(obj);
2206 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir = '%s%s/%s/%s'", odbc_table, VM_SPOOL_DIR, context, tmp, "INBOX");
2207 res = SQLPrepare(stmt, sql, SQL_NTS);
2208 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2209 ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql);
2210 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2211 odbc_release_obj(obj);
2214 res = odbc_smart_execute(obj, stmt);
2215 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2216 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
2217 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2218 odbc_release_obj(obj);
2221 res = SQLFetch(stmt);
2222 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2223 ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
2224 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2225 odbc_release_obj(obj);
2228 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
2229 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2230 ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
2231 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2232 odbc_release_obj(obj);
2235 *newmsgs = atoi(rowdata);
2236 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2238 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
2239 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2240 ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
2241 odbc_release_obj(obj);
2244 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir = '%s%s/%s/%s'", odbc_table, VM_SPOOL_DIR, context, tmp, "Old");
2245 res = SQLPrepare(stmt, sql, SQL_NTS);
2246 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2247 ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql);
2248 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2249 odbc_release_obj(obj);
2252 res = odbc_smart_execute(obj, stmt);
2253 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2254 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
2255 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2256 odbc_release_obj(obj);
2259 res = SQLFetch(stmt);
2260 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2261 ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
2262 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2263 odbc_release_obj(obj);
2266 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
2267 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2268 ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
2269 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2270 odbc_release_obj(obj);
2273 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2274 odbc_release_obj(obj);
2275 *oldmsgs = atoi(rowdata);
2278 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
2284 static int messagecount(const char *context, const char *mailbox, const char *folder)
2286 struct odbc_obj *obj = NULL;
2289 SQLHSTMT stmt = NULL;
2294 /* If no mailbox, return immediately */
2295 if (ast_strlen_zero(mailbox))
2298 obj = odbc_request_obj(odbc_database, 0);
2300 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
2301 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2302 ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
2305 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir = '%s%s/%s/%s'", odbc_table, VM_SPOOL_DIR, context, mailbox, folder);
2306 res = SQLPrepare(stmt, sql, SQL_NTS);
2307 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2308 ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql);
2309 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
2312 res = odbc_smart_execute(obj, stmt);
2313 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2314 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
2315 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2318 res = SQLFetch(stmt);
2319 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2320 ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
2321 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2324 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
2325 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2326 ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
2327 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2330 nummsgs = atoi(rowdata);
2331 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2333 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
2337 odbc_release_obj(obj);
2341 static int has_voicemail(const char *mailbox, const char *folder)
2343 char *context, tmp[256];
2344 ast_copy_string(tmp, mailbox, sizeof(tmp));
2345 if ((context = strchr(tmp, '@')))
2348 context = "default";
2350 if (messagecount(context, tmp, folder))
2358 static int count_messages_imap(const char *mailbox, int *newmsgs, int *oldmsgs)
2363 struct ast_vm_user *vmu;
2364 struct vm_state *vms_p;
2375 if(option_debug > 2)
2376 ast_log (LOG_DEBUG,"Mailbox is set to %s\n",mailbox);
2377 /* If no mailbox, return immediately */
2378 if (ast_strlen_zero(mailbox))
2380 if (strchr(mailbox, ',')) {
2382 ast_copy_string(tmp, mailbox, sizeof(tmp));
2385 while((cur = strsep(&mb, ", "))) {
2386 if (!ast_strlen_zero(cur)) {
2387 if (count_messages_imap(cur, newmsgs ? &tmpnew : NULL, oldmsgs ? &tmpold : NULL))
2399 ast_copy_string(tmp, mailbox, sizeof(tmp));
2400 context = strchr(tmp, '@');
2406 context = "default";
2407 mailboxnc = (char *)mailbox;
2410 /* We have to get the user before we can open the stream! */
2411 /*ast_log (LOG_DEBUG,"Before find_user, context is %s and mailbox is %s\n",context,mailbox); */
2412 vmu = find_user(NULL, context, mailboxnc);
2414 ast_log (LOG_ERROR,"Couldn't find mailbox %s in context %s\n",mailboxnc,context);
2417 /* No IMAP account available */
2418 if (vmu->imapuser[0] == '\0') {
2419 ast_log (LOG_WARNING,"IMAP user not set for mailbox %s\n",vmu->mailbox);
2424 /* check if someone is accessing this box right now... */
2425 vms_p = get_vm_state_by_imapuser(vmu->imapuser,1);
2427 vms_p = get_vm_state_by_mailbox(mailboxnc,1);
2430 if(option_debug > 2)
2431 ast_log (LOG_DEBUG,"Returning before search - user is logged in\n");
2432 *newmsgs = vms_p->newmessages;
2433 *oldmsgs = vms_p->oldmessages;
2437 /* add one if not there... */
2438 vms_p = get_vm_state_by_imapuser(vmu->imapuser,0);
2440 vms_p = get_vm_state_by_mailbox(mailboxnc,0);
2444 if(option_debug > 2)
2445 ast_log (LOG_DEBUG,"Adding new vmstate for %s\n",vmu->imapuser);
2446 vms_p = (struct vm_state *)malloc(sizeof(struct vm_state));
2447 strcpy(vms_p->imapuser,vmu->imapuser);
2448 ast_copy_string(vms_p->username, mailboxnc, sizeof(vms_p->username)); /* save for access from interactive entry point */
2449 vms_p->mailstream = NIL; /* save for access from interactive entry point */
2450 if(option_debug > 2)
2451 ast_log (LOG_DEBUG,"Copied %s to %s\n",vmu->imapuser,vms_p->imapuser);
2453 vms_p->interactive = 0;
2454 /* set mailbox to INBOX! */
2455 ast_copy_string(vms_p->curbox, mbox(0), sizeof(vms_p->curbox));
2456 init_vm_state(vms_p);
2457 vmstate_insert(vms_p);
2459 if (!vms_p->mailstream)
2460 ret = init_mailstream(vms_p);
2461 if (!vms_p->mailstream) {
2462 ast_log (LOG_ERROR,"Houston we have a problem - IMAP mailstream is NULL\n");
2465 if (newmsgs && ret==0 && vms_p->updated==1 ) {
2466 pgm = mail_newsearchpgm ();
2467 hdr = mail_newsearchheader ("X-Asterisk-VM-Extension", (char *)mailboxnc);
2474 vms_p->vmArrayIndex = 0;
2476 mail_search_full (vms_p->mailstream, NULL, pgm, NIL);
2477 *newmsgs = vms_p->vmArrayIndex;
2478 vms_p->newmessages = vms_p->vmArrayIndex;
2480 if (oldmsgs && ret==0 && vms_p->updated==1 ) {
2481 pgm = mail_newsearchpgm ();
2482 hdr = mail_newsearchheader ("X-Asterisk-VM-Extension", (char *)mailboxnc);
2489 vms_p->vmArrayIndex = 0;
2491 mail_search_full (vms_p->mailstream, NULL, pgm, NIL);
2492 *oldmsgs = vms_p->vmArrayIndex;
2493 vms_p->oldmessages = vms_p->vmArrayIndex;
2495 if (vms_p->updated == 1) { /* changes, so we did the searches above */
2497 } else if (vms_p->updated > 1) { /* decrement delay count */
2499 } else { /* no changes, so don't search */
2500 mail_ping(vms_p->mailstream);
2501 /* Keep the old data */
2502 *newmsgs = vms_p->newmessages;
2503 *oldmsgs = vms_p->oldmessages;
2510 /* copy message only used by file storage */
2511 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)
2514 char fromdir[256], todir[256], frompath[256], topath[256];
2515 const char *frombox = mbox(imbox);
2518 ast_log(LOG_NOTICE, "Copying message from %s@%s to %s@%s\n", vmu->mailbox, vmu->context, recip->mailbox, recip->context);
2520 create_dirpath(todir, sizeof(todir), recip->context, recip->mailbox, "INBOX");
2522 make_dir(fromdir, sizeof(fromdir), vmu->context, vmu->mailbox, frombox);
2523 make_file(frompath, sizeof(frompath), fromdir, msgnum);
2525 if (vm_lock_path(todir))
2526 return ERROR_LOCK_PATH;
2530 make_file(topath, sizeof(topath), todir, recipmsgnum);
2531 if (!EXISTS(todir, recipmsgnum, topath, chan->language))
2534 } while (recipmsgnum < recip->maxmsg);
2535 if (recipmsgnum < recip->maxmsg) {
2536 COPY(fromdir, msgnum, todir, recipmsgnum, recip->mailbox, recip->context, frompath, topath);
2538 ast_log(LOG_ERROR, "Recipient mailbox %s@%s is full\n", recip->mailbox, recip->context);
2540 ast_unlock_path(todir);
2541 notify_new_message(chan, recip, recipmsgnum, duration, fmt, chan->cid.cid_num, chan->cid.cid_name);
2546 #ifndef ODBC_STORAGE
2548 static int messagecount(const char *context, const char *mailbox, const char *folder)
2550 return __has_voicemail(context, mailbox, folder, 0);
2554 static int __has_voicemail(const char *context, const char *mailbox, const char *folder, int shortcircuit)
2562 /* If no mailbox, return immediately */
2563 if (ast_strlen_zero(mailbox))
2566 context = "default";
2567 snprintf(fn, sizeof(fn), "%s%s/%s/%s", VM_SPOOL_DIR, context, mailbox, folder);
2571 while ((de = readdir(dir))) {
2572 if (!strncasecmp(de->d_name, "msg", 3)) {
2576 } else if (!strncasecmp(de->d_name + 8, "txt", 3))
2585 static int has_voicemail(const char *mailbox, const char *folder)
2587 char tmp[256], *tmp2 = tmp, *mbox, *context;
2588 ast_copy_string(tmp, mailbox, sizeof(tmp));
2589 while ((mbox = strsep(&tmp2, ","))) {
2590 if ((context = strchr(mbox, '@')))
2593 context = "default";
2594 if (__has_voicemail(context, mbox, folder, 1))
2601 static int inboxcount(const char *mailbox, int *newmsgs, int *oldmsgs)
2610 /* If no mailbox, return immediately */
2611 if (ast_strlen_zero(mailbox))
2613 if (strchr(mailbox, ',')) {
2617 ast_copy_string(tmp, mailbox, sizeof(tmp));
2619 while ((cur = strsep(&mb, ", "))) {
2620 if (!ast_strlen_zero(cur)) {
2621 if (inboxcount(cur, newmsgs ? &tmpnew : NULL, oldmsgs ? &tmpold : NULL))
2633 ast_copy_string(tmp, mailbox, sizeof(tmp));
2634 context = strchr(tmp, '@');
2639 context = "default";
2641 *newmsgs = __has_voicemail(context, tmp, "INBOX", 0);
2643 *oldmsgs = __has_voicemail(context, tmp, "Old", 0);
2649 static void run_externnotify(char *context, char *extension)
2651 char arguments[255];
2652 char ext_context[256] = "";
2653 int newvoicemails = 0, oldvoicemails = 0;
2654 struct ast_smdi_mwi_message *mwi_msg;
2656 if (!ast_strlen_zero(context))
2657 snprintf(ext_context, sizeof(ext_context), "%s@%s", extension, context);
2659 ast_copy_string(ext_context, extension, sizeof(ext_context));
2661 if (!strcasecmp(externnotify, "smdi")) {
2662 if (ast_app_has_voicemail(ext_context, NULL))
2663 ast_smdi_mwi_set(smdi_iface, extension);
2665 ast_smdi_mwi_unset(smdi_iface, extension);
2667 if ((mwi_msg = ast_smdi_mwi_message_wait(smdi_iface, SMDI_MWI_WAIT_TIMEOUT))) {
2668 ast_log(LOG_ERROR, "Error executing SMDI MWI change for %s on %s\n", extension, smdi_iface->name);
2669 if (!strncmp(mwi_msg->cause, "INV", 3))
2670 ast_log(LOG_ERROR, "Invalid MWI extension: %s\n", mwi_msg->fwd_st);
2671 else if (!strncmp(mwi_msg->cause, "BLK", 3))
2672 ast_log(LOG_WARNING, "MWI light was already on or off for %s\n", mwi_msg->fwd_st);
2673 ast_log(LOG_WARNING, "The switch reported '%s'\n", mwi_msg->cause);
2674 ASTOBJ_UNREF(mwi_msg, ast_smdi_mwi_message_destroy);
2676 ast_log(LOG_DEBUG, "Successfully executed SMDI MWI change for %s on %s\n", extension, smdi_iface->name);
2678 } else if (!ast_strlen_zero(externnotify)) {
2679 if (inboxcount(ext_context, &newvoicemails, &oldvoicemails)) {
2680 ast_log(LOG_ERROR, "Problem in calculating number of voicemail messages available for extension %s\n", extension);
2682 snprintf(arguments, sizeof(arguments), "%s %s %s %d&", externnotify, context, extension, newvoicemails);
2683 ast_log(LOG_DEBUG, "Executing %s\n", arguments);
2684 ast_safe_system(arguments);
2689 struct leave_vm_options {
2691 signed char record_gain;
2694 static int leave_voicemail(struct ast_channel *chan, char *ext, struct leave_vm_options *options)
2697 int newmsgs, oldmsgs;
2698 struct vm_state *vms;
2700 char tmptxtfile[256], txtfile[256];
2712 char dir[256], tmpdir[260];
2714 char prefile[256]="";
2715 char tempfile[256]="";
2716 char ext_context[256] = "";
2719 char ecodes[16] = "#";
2720 char tmp[256] = "", *tmpptr;
2721 struct ast_vm_user *vmu;
2722 struct ast_vm_user svm;
2723 const char *category = NULL;
2725 ast_copy_string(tmp, ext, sizeof(tmp));
2727 context = strchr(tmp, '@');
2730 tmpptr = strchr(context, '&');
2732 tmpptr = strchr(ext, '&');
2738 category = pbx_builtin_getvar_helper(chan, "VM_CATEGORY");
2740 if(option_debug > 2)
2741 ast_log(LOG_DEBUG, "Before find_user\n");
2742 if (!(vmu = find_user(&svm, context, ext))) {
2743 ast_log(LOG_WARNING, "No entry in voicemail config file for '%s'\n", ext);
2744 if (ast_test_flag(options, OPT_PRIORITY_JUMP) || ast_opt_priority_jumping)
2745 ast_goto_if_exists(chan, chan->context, chan->exten, chan->priority + 101);
2746 pbx_builtin_setvar_helper(chan, "VMSTATUS", "FAILED");
2750 /* Setup pre-file if appropriate */
2751 if (strcmp(vmu->context, "default"))
2752 snprintf(ext_context, sizeof(ext_context), "%s@%s", ext, vmu->context);
2754 ast_copy_string(ext_context, vmu->context, sizeof(ext_context));
2755 if (ast_test_flag(options, OPT_BUSY_GREETING))
2756 snprintf(prefile, sizeof(prefile), "%s%s/%s/busy", VM_SPOOL_DIR, vmu->context, ext);
2757 else if (ast_test_flag(options, OPT_UNAVAIL_GREETING))
2758 snprintf(prefile, sizeof(prefile), "%s%s/%s/unavail", VM_SPOOL_DIR, vmu->context, ext);
2759 snprintf(tempfile, sizeof(tempfile), "%s%s/%s/temp", VM_SPOOL_DIR, vmu->context, ext);
2760 RETRIEVE(tempfile, -1);
2761 if (ast_fileexists(tempfile, NULL, NULL) > 0)
2762 ast_copy_string(prefile, tempfile, sizeof(prefile));
2763 DISPOSE(tempfile, -1);
2764 /* It's easier just to try to make it than to check for its existence */
2765 create_dirpath(dir, sizeof(dir), vmu->context, ext, "INBOX");
2766 create_dirpath(tmpdir, sizeof(tmpdir), vmu->context, ext, "tmp");
2768 /* Check current or macro-calling context for special extensions */
2769 if (ast_test_flag(vmu, VM_OPERATOR)) {
2770 if (!ast_strlen_zero(vmu->exit)) {
2771 if (ast_exists_extension(chan, vmu->exit, "o", 1, chan->cid.cid_num)) {
2772 strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
2775 } else if (ast_exists_extension(chan, chan->context, "o", 1, chan->cid.cid_num)) {
2776 strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
2779 else if (!ast_strlen_zero(chan->macrocontext) && ast_exists_extension(chan, chan->macrocontext, "o", 1, chan->cid.cid_num)) {
2780 strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
2785 if (!ast_strlen_zero(vmu->exit)) {
2786 if (ast_exists_extension(chan, vmu->exit, "a", 1, chan->cid.cid_num))
2787 strncat(ecodes, "*", sizeof(ecodes) - strlen(ecodes) - 1);
2788 } else if (ast_exists_extension(chan, chan->context, "a", 1, chan->cid.cid_num))
2789 strncat(ecodes, "*", sizeof(ecodes) - strlen(ecodes) - 1);
2790 else if (!ast_strlen_zero(chan->macrocontext) && ast_exists_extension(chan, chan->macrocontext, "a", 1, chan->cid.cid_num)) {
2791 strncat(ecodes, "*", sizeof(ecodes) - strlen(ecodes) - 1);
2795 /* Play the beginning intro if desired */
2796 if (!ast_strlen_zero(prefile)) {
2797 RETRIEVE(prefile, -1);
2798 if (ast_fileexists(prefile, NULL, NULL) > 0) {
2799 if (ast_streamfile(chan, prefile, chan->language) > -1)
2800 res = ast_waitstream(chan, ecodes);
2802 ast_log(LOG_DEBUG, "%s doesn't exist, doing what we can\n", prefile);
2803 res = invent_message(chan, vmu->context, ext, ast_test_flag(options, OPT_BUSY_GREETING), ecodes);
2805 DISPOSE(prefile, -1);
2807 ast_log(LOG_DEBUG, "Hang up during prefile playback\n");
2809 pbx_builtin_setvar_helper(chan, "VMSTATUS", "FAILED");
2814 /* On a '#' we skip the instructions */
2815 ast_set_flag(options, OPT_SILENT);
2818 if (!res && !ast_test_flag(options, OPT_SILENT)) {
2819 res = ast_stream_and_wait(chan, INTRO, chan->language, ecodes);
2821 ast_set_flag(options, OPT_SILENT);
2826 ast_stopstream(chan);
2827 /* Check for a '*' here in case the caller wants to escape from voicemail to something
2828 other than the operator -- an automated attendant or mailbox login for example */
2830 chan->exten[0] = 'a';
2831 chan->exten[1] = '\0';
2832 if (!ast_strlen_zero(vmu->exit)) {
2833 ast_copy_string(chan->context, vmu->exit, sizeof(chan->context));
2834 } else if (ausemacro && !ast_strlen_zero(chan->macrocontext)) {
2835 ast_copy_string(chan->context, chan->macrocontext, sizeof(chan->context));
2839 pbx_builtin_setvar_helper(chan, "VMSTATUS", "USEREXIT");
2843 /* Check for a '0' here */
2846 if (ouseexten || ousemacro) {
2847 chan->exten[0] = 'o';
2848 chan->exten[1] = '\0';
2849 if (!ast_strlen_zero(vmu->exit)) {
2850 ast_copy_string(chan->context, vmu->exit, sizeof(chan->context));
2851 } else if (ousemacro && !ast_strlen_zero(chan->macrocontext)) {
2852 ast_copy_string(chan->context, chan->macrocontext, sizeof(chan->context));
2854 ast_play_and_wait(chan, "transfer");
2857 pbx_builtin_setvar_helper(chan, "VMSTATUS", "USEREXIT");
2863 pbx_builtin_setvar_helper(chan, "VMSTATUS", "FAILED");
2866 /* The meat of recording the message... All the announcements and beeps have been played*/
2867 ast_copy_string(fmt, vmfmts, sizeof(fmt));
2868 if (!ast_strlen_zero(fmt)) {
2872 /* Is ext a mailbox? */
2873 /* must open stream for this user to get info! */
2874 vms = get_vm_state_by_mailbox(ext,0);
2876 if(option_debug > 2)
2877 ast_log(LOG_DEBUG, "Using vm_state, interactive set to %d.\n",vms->interactive);
2878 newmsgs = vms->newmessages++;
2879 oldmsgs = vms->oldmessages;
2881 res = count_messages_imap(ext, &newmsgs, &oldmsgs);
2883 ast_log(LOG_NOTICE,"Can not leave voicemail, unable to count messages\n");
2887 /* here is a big difference! We add one to it later */
2888 msgnum = newmsgs + oldmsgs;
2889 ast_log(LOG_NOTICE, "Messagecount set to %d\n",msgnum);
2890 snprintf(fn, sizeof(fn), "%s/imap/msg%s%04d", VM_SPOOL_DIR, vmu->mailbox, msgnum);
2891 /* set variable for compatability */
2892 pbx_builtin_setvar_helper(chan, "VM_MESSAGEFILE", "IMAP_STORAGE");
2894 /* Check if mailbox is full */
2895 if (vms->quota_usage >= vms->quota_limit) {
2896 ast_log(LOG_DEBUG, "*** QUOTA EXCEEDED!!\n");
2897 ast_play_and_wait(chan, "vm-mailboxfull");
2902 res = ast_streamfile(chan, "beep", chan->language);
2904 res = ast_waitstream(chan, "");
2905 /* play_record_review does recording and verify */
2906 ast_log(LOG_DEBUG, "About to record message in file %s\n",fn);
2907 res = play_record_review(chan, NULL, fn, vmmaxmessage, fmt, 1, vmu, &duration, dir, options->record_gain);
2911 if (res > 0) res = 0;
2913 if (duration < vmminmessage) {
2914 if (option_verbose > 2)
2915 ast_verbose( VERBOSE_PREFIX_3 "Recording was %d seconds long but needs to be at least %d - abandoning\n", duration, vmminmessage);
2918 notify_new_message(chan, vmu, msgnum, duration, fmt, chan->cid.cid_num, chan->cid.cid_name);
2920 if (count_messages(vmu, dir) >= vmu->maxmsg) {
2921 res = ast_streamfile(chan, "vm-mailboxfull", chan->language);
2923 res = ast_waitstream(chan, "");
2924 ast_log(LOG_WARNING, "No more messages possible\n");
2925 pbx_builtin_setvar_helper(chan, "VMSTATUS", "FAILED");
2929 snprintf(tmptxtfile, sizeof(tmptxtfile), "%s/XXXXXX", tmpdir);
2930 txtdes = mkstemp(tmptxtfile);
2932 res = ast_streamfile(chan, "vm-mailboxfull", chan->language);
2934 res = ast_waitstream(chan, "");
2935 ast_log(LOG_ERROR, "Unable to create message file: %s\n", strerror(errno));
2936 pbx_builtin_setvar_helper(chan, "VMSTATUS", "FAILED");
2940 /* Now play the beep once we have the message number for our next message. */
2942 /* Unless we're *really* silent, try to send the beep */
2943 res = ast_stream_and_wait(chan, "beep", chan->language, "");
2946 /* Store information */
2947 txt = fdopen(txtdes, "w+");
2949 get_date(date, sizeof(date));
2952 "; Message Information file\n"
2971 ast_callerid_merge(callerid, sizeof(callerid), chan->cid.cid_name, chan->cid.cid_num, "Unknown"),
2972 date, (long)time(NULL),
2973 category ? category : "");
2975 ast_log(LOG_WARNING, "Error opening text file for output\n");
2976 res = play_record_review(chan, NULL, tmptxtfile, vmmaxmessage, fmt, 1, vmu, &duration, NULL, options->record_gain);
2979 if (duration < vmminmessage) {
2980 if (option_verbose > 2)
2981 ast_verbose( VERBOSE_PREFIX_3 "Recording was %d seconds long but needs to be at least %d - abandoning\n", duration, vmminmessage);
2982 ast_filedelete(tmptxtfile, NULL);
2985 fprintf(txt, "duration=%d\n", duration);
2987 if (vm_lock_path(dir)) {
2988 ast_log(LOG_ERROR, "Couldn't lock directory %s. Voicemail will be lost.\n", dir);
2990 ast_filedelete(tmptxtfile, NULL);
2992 } else if (ast_fileexists(tmptxtfile, NULL, NULL) <= 0) {
2994 ast_log(LOG_DEBUG, "The recorded media file is gone, so we should remove the .txt file too!\n");
2996 ast_unlock_path(dir);
2999 make_file(fn, sizeof(fn), dir, msgnum);
3000 if (!EXISTS(dir, msgnum, fn, NULL))
3005 /* assign a variable with the name of the voicemail file */
3006 pbx_builtin_setvar_helper(chan, "VM_MESSAGEFILE", fn);
3008 snprintf(txtfile, sizeof(txtfile), "%s.txt", fn);
3009 ast_filerename(tmptxtfile, fn, NULL);
3010 rename(tmptxtfile, txtfile);
3012 ast_unlock_path(dir);
3013 /* Are there to be more recipients of this message? */