2 * Asterisk -- A telephony toolkit for Linux.
4 * Voicemail System (did you ever think it could be so easy?)
6 * Copyright (C) 1999, Mark Spencer
8 * Mark Spencer <markster@linux-support.net>
10 * This program is free software, distributed under the terms of
11 * the GNU General Public License
14 #include <asterisk/file.h>
15 #include <asterisk/logger.h>
16 #include <asterisk/channel.h>
17 #include <asterisk/pbx.h>
18 #include <asterisk/options.h>
19 #include <asterisk/config.h>
20 #include <asterisk/say.h>
21 #include <asterisk/module.h>
33 #include "../asterisk.h"
35 #define COMMAND_TIMEOUT 5000
37 #define VOICEMAIL_CONFIG "voicemail.conf"
38 #define ASTERISK_USERNAME "asterisk"
40 #define SENDMAIL "/usr/sbin/sendmail -t"
42 #define INTRO "vm-intro"
46 #define MAX_OTHER_FORMATS 10
48 #define VM_SPOOL_DIR AST_SPOOL_DIR "/vm"
51 static char *tdesc = "Comedian Mail (Voicemail System)";
53 static char *synopsis_vm =
54 "Leave a voicemail message";
56 static char *descrip_vm =
57 " VoiceMail([s]extension): Leaves voicemail for a given extension (must be\n"
58 "configured in voicemail.conf). If the extension is preceeded by an 's' then\n"
59 "instructions for leaving the message will be skipped. Returns -1 on error\n"
60 "or mailbox not found, or if the user hangs up. Otherwise, it returns 0. \n";
62 static char *synopsis_vmain =
63 "Enter voicemail system";
65 static char *descrip_vmain =
66 " VoiceMailMain(): Enters the main voicemail system for the checking of voicemail. Returns\n"
67 " -1 if the user hangs up or 0 otherwise.\n";
70 static char *app = "VoiceMail";
72 /* Check mail, control, etc */
73 static char *app2 = "VoiceMailMain";
79 static int make_dir(char *dest, int len, char *ext, char *mailbox)
81 return snprintf(dest, len, "%s/%s/%s", VM_SPOOL_DIR, ext, mailbox);
84 static int make_file(char *dest, int len, char *dir, int num)
86 return snprintf(dest, len, "%s/msg%04d", dir, num);
91 static int announce_message(struct ast_channel *chan, char *dir, int msgcnt)
95 res = ast_streamfile(chan, "vm-message", chan->language);
97 res = ast_waitstream(chan, AST_DIGIT_ANY);
99 res = ast_say_number(chan, msgcnt+1, chan->language);
101 fn = get_fn(dir, msgcnt);
103 res = ast_streamfile(chan, fn, chan->language);
110 ast_log(LOG_WARNING, "Unable to announce message\n");
115 static int sendmail(char *srcemail, char *email, char *name, int msgnum, char *mailbox, char *callerid)
123 p = popen(SENDMAIL, "w");
125 if (strchr(srcemail, '@'))
126 strncpy(who, srcemail, sizeof(who));
128 gethostname(host, sizeof(host));
129 snprintf(who, sizeof(who), "%s@%s", srcemail, host);
133 strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", tm);
134 fprintf(p, "Date: %s\n", date);
135 fprintf(p, "Message-ID: <Asterisk-%d-%s-%d@%s>\n", msgnum, mailbox, getpid(), host);
136 fprintf(p, "From: Asterisk PBX <%s>\n", who);
137 fprintf(p, "To: %s <%s>\n", name, email);
138 fprintf(p, "Subject: [PBX]: New message %d in mailbox %s\n\n", msgnum, mailbox);
139 strftime(date, sizeof(date), "%A, %B %d, %Y at %r", tm);
140 fprintf(p, "Dear %s:\n\n\tJust wanted to let you know you were just left a message (number %d)\n"
141 "in mailbox %s from %s, on %s so you might\n"
142 "want to check it when you get a chance. Thanks!\n\n\t\t\t\t--Asterisk\n", name,
143 msgnum, mailbox, (callerid ? callerid : "an unknown caller"), date);
147 ast_log(LOG_WARNING, "Unable to launch '%s'\n", SENDMAIL);
153 static int get_date(char *s, int len)
159 return strftime(s, len, "%a %b %e %r %Z %Y", tm);
162 static int leave_voicemail(struct ast_channel *chan, char *ext, int silent)
164 struct ast_config *cfg;
165 char *copy, *name, *passwd, *email, *fmt, *fmts;
167 struct ast_filestream *writer=NULL, *others[MAX_OTHER_FORMATS];
168 char *sfmt[MAX_OTHER_FORMATS];
171 int res = -1, fmtcnt=0, x;
180 cfg = ast_load(VOICEMAIL_CONFIG);
182 ast_log(LOG_WARNING, "No such configuration file %s\n", VOICEMAIL_CONFIG);
185 if (!(astemail = ast_variable_retrieve(cfg, "general", "serveremail")))
186 astemail = ASTERISK_USERNAME;
187 if ((copy = ast_variable_retrieve(cfg, NULL, ext))) {
188 /* Make sure they have an entry in the config */
190 passwd = strtok(copy, ",");
191 name = strtok(NULL, ",");
192 email = strtok(NULL, ",");
193 make_dir(dir, sizeof(dir), ext, "");
194 /* It's easier just to try to make it than to check for its existence */
195 if (mkdir(dir, 0700) && (errno != EEXIST))
196 ast_log(LOG_WARNING, "mkdir '%s' failed: %s\n", dir, strerror(errno));
197 make_dir(dir, sizeof(dir), ext, "INBOX");
198 if (mkdir(dir, 0700) && (errno != EEXIST))
199 ast_log(LOG_WARNING, "mkdir '%s' failed: %s\n", dir, strerror(errno));
200 /* Stream an info message */
201 if (silent || !ast_streamfile(chan, INTRO, chan->language)) {
202 /* Wait for the message to finish */
203 if (silent || !ast_waitstream(chan, "")) {
204 fmt = ast_variable_retrieve(cfg, "general", "format");
207 fmt = strtok(fmts, "|");
210 make_file(fn, sizeof(fn), dir, msgnum);
211 snprintf(comment, sizeof(comment), "Voicemail from %s to %s (%s) on %s\n",
212 (chan->callerid ? chan->callerid : "Unknown"),
213 name, ext, chan->name);
214 if (ast_fileexists(fn, NULL, chan->language) > 0) {
218 writer = ast_writefile(fn, fmt, comment, O_EXCL, 1 /* check for other formats */, 0700);
222 } while(!writer && (msgnum < MAXMSG));
224 /* Store information */
225 snprintf(txtfile, sizeof(txtfile), "%s.txt", fn);
226 txt = fopen(txtfile, "w+");
228 get_date(date, sizeof(date));
231 "# Message Information file\n"
245 chan->callerid ? chan->callerid : "Unknown",
249 ast_log(LOG_WARNING, "Error opening text file for output\n");
251 /* We need to reset these values */
253 fmt = ast_variable_retrieve(cfg, "general", "format");
256 while((fmt = strtok(NULL, "|"))) {
257 if (fmtcnt > MAX_OTHER_FORMATS - 1) {
258 ast_log(LOG_WARNING, "Please increase MAX_OTHER_FORMATS in app_voicemail.c\n");
261 sfmt[fmtcnt++] = strdup(fmt);
263 for (x=0;x<fmtcnt;x++) {
264 others[x] = ast_writefile(fn, sfmt[x], comment, 0, 0, 0700);
266 /* Ick, the other format didn't work, but be sure not
267 to leak memory here */
269 for(y=x+1;y < fmtcnt;y++)
276 /* Loop forever, writing the packets we read to the writer(s), until
277 we read a # or get a hangup */
278 if (option_verbose > 2)
279 ast_verbose( VERBOSE_PREFIX_3 "Recording to %s\n", fn);
280 while((f = ast_read(chan))) {
281 if (f->frametype == AST_FRAME_VOICE) {
282 /* Write the primary format */
283 res = ast_writestream(writer, f);
285 ast_log(LOG_WARNING, "Error writing primary frame\n");
288 /* And each of the others */
289 for (x=0;x<fmtcnt;x++) {
290 res |= ast_writestream(others[x], f);
293 /* Exit on any error */
295 ast_log(LOG_WARNING, "Error writing frame\n");
299 if (f->frametype == AST_FRAME_DTMF) {
300 if (f->subclass == '#') {
301 if (option_verbose > 2)
302 ast_verbose( VERBOSE_PREFIX_3 "User ended message by pressing %c\n", f->subclass);
309 if (option_verbose > 2)
310 ast_verbose( VERBOSE_PREFIX_3 "User hung up\n");
315 ast_log(LOG_WARNING, "Error creating writestream '%s', format '%s'\n", fn, sfmt[x]);
318 ast_closestream(writer);
319 for (x=0;x<fmtcnt;x++) {
322 ast_closestream(others[x]);
326 /* Let them know it worked */
327 ast_streamfile(chan, "vm-msgsaved", chan->language);
328 ast_waitstream(chan, "");
330 /* Send e-mail if applicable */
332 sendmail(astemail, email, name, msgnum, ext, chan->callerid);
336 ast_log(LOG_WARNING, "Error writing to mailbox %s\n", ext);
338 ast_log(LOG_WARNING, "Too many messages in mailbox %s\n", ext);
342 ast_log(LOG_WARNING, "No format to save messages in \n");
345 ast_log(LOG_WARNING, "Unable to playback instructions\n");
349 ast_log(LOG_WARNING, "No entry in voicemail config file for '%s'\n", ext);
351 /* Leave voicemail for someone */
355 static char *mbox(int id)
383 static int count_messages(char *dir)
387 for (x=0;x<MAXMSG;x++) {
388 make_file(fn, sizeof(fn), dir, x);
389 if (ast_fileexists(fn, NULL, NULL) < 1)
395 static int play_and_wait(struct ast_channel *chan, char *fn)
398 d = ast_streamfile(chan, fn, chan->language);
401 d = ast_waitstream(chan, AST_DIGIT_ANY);
405 static int say_and_wait(struct ast_channel *chan, int num)
408 d = ast_say_number(chan, num, chan->language);
412 static int copy(char *infile, char *outfile)
419 if ((ifd = open(infile, O_RDONLY)) < 0) {
420 ast_log(LOG_WARNING, "Unable to open %s in read-only mode\n", infile);
423 if ((ofd = open(outfile, O_WRONLY | O_TRUNC | O_CREAT, 0600)) < 0) {
424 ast_log(LOG_WARNING, "Unable to open %s in write-only mode\n", outfile);
429 len = read(ifd, buf, sizeof(buf));
431 ast_log(LOG_WARNING, "Read failed on %s: %s\n", infile, strerror(errno));
437 res = write(ofd, buf, len);
439 ast_log(LOG_WARNING, "Write failed on %s (%d of %d): %s\n", outfile, res, len, strerror(errno));
451 static int save_to_folder(char *dir, int msg, char *username, int box)
458 char *dbox = mbox(box);
460 make_file(sfn, sizeof(sfn), dir, msg);
461 make_dir(ddir, sizeof(ddir), username, dbox);
463 for (x=0;x<MAXMSG;x++) {
464 make_file(dfn, sizeof(dfn), ddir, x);
465 if (ast_fileexists(dfn, NULL, NULL) < 0)
470 ast_filecopy(sfn, dfn, NULL);
471 if (strcmp(sfn, dfn)) {
472 snprintf(txt, sizeof(txt), "%s.txt", sfn);
473 snprintf(ntxt, sizeof(ntxt), "%s.txt", dfn);
479 static int get_folder(struct ast_channel *chan, int start)
484 d = play_and_wait(chan, "vm-press");
487 for (x = start; x< 5; x++) {
488 if ((d = ast_say_number(chan, x, chan->language)))
490 d = play_and_wait(chan, "vm-for");
493 snprintf(fn, sizeof(fn), "vm-%s", mbox(x));
494 d = play_and_wait(chan, fn);
497 d = play_and_wait(chan, "vm-messages");
500 d = ast_waitfordigit(chan, 500);
504 d = play_and_wait(chan, "vm-tocancel");
507 d = ast_waitfordigit(chan, 4000);
511 #define WAITCMD(a) do { \
519 #define WAITFILE2(file) do { \
520 if (ast_streamfile(chan, file, chan->language)) \
521 ast_log(LOG_WARNING, "Unable to play message %s\n", file); \
522 d = ast_waitstream(chan, AST_DIGIT_ANY); \
528 #define WAITFILE(file) do { \
529 if (ast_streamfile(chan, file, chan->language)) \
530 ast_log(LOG_WARNING, "Unable to play message %s\n", file); \
531 d = ast_waitstream(chan, AST_DIGIT_ANY); \
535 } else if (d < 0) { \
540 #define PLAYMSG(a) do { \
543 WAITFILE2("vm-first"); \
544 else if (a == lastmsg) \
545 WAITFILE2("vm-last"); \
546 WAITFILE2("vm-message"); \
547 if (a && (a != lastmsg)) { \
548 d = ast_say_number(chan, a + 1, chan->language); \
549 if (d < 0) goto out; \
552 make_file(fn, sizeof(fn), curdir, a); \
557 #define CLOSE_MAILBOX do { \
558 if (lastmsg > -1) { \
559 /* Get the deleted messages fixed */ \
561 for (x=0;x<=lastmsg;x++) { \
562 if (!deleted[x] && (strcasecmp(curbox, "INBOX") || !heard[x])) { \
563 /* Save this message. It's not in INBOX or hasn't been heard */ \
565 make_file(fn, sizeof(fn), curdir, x); \
566 make_file(fn2, sizeof(fn2), curdir, curmsg); \
567 if (strcmp(fn, fn2)) { \
568 snprintf(txt, sizeof(txt), "%s.txt", fn); \
569 snprintf(ntxt, sizeof(ntxt), "%s.txt", fn2); \
570 ast_filerename(fn, fn2, NULL); \
573 } else if (!strcasecmp(curbox, "INBOX") && heard[x] && !deleted[x]) { \
574 /* Move to old folder before deleting */ \
575 save_to_folder(curdir, x, username, 1); \
578 for (x = curmsg + 1; x<=lastmsg; x++) { \
579 make_file(fn, sizeof(fn), curdir, x); \
580 snprintf(txt, sizeof(txt), "%s.txt", fn); \
581 ast_filedelete(fn, NULL); \
585 memset(deleted, 0, sizeof(deleted)); \
586 memset(heard, 0, sizeof(heard)); \
589 #define OPEN_MAILBOX(a) do { \
590 strcpy(curbox, mbox(a)); \
591 make_dir(curdir, sizeof(curdir), username, curbox); \
592 lastmsg = count_messages(curdir) - 1; \
593 snprintf(vmbox, sizeof(vmbox), "vm-%s", curbox); \
596 static int vm_execmain(struct ast_channel *chan, void *data)
598 /* XXX This is, admittedly, some pretty horrendus code. For some
599 reason it just seemed a lot easier to do with GOTO's. I feel
600 like I'm back in my GWBASIC days. XXX */
606 char password[80], *copy;
615 int deleted[MAXMSG] = { 0, };
616 int heard[MAXMSG] = { 0, };
624 struct ast_config *cfg;
627 cfg = ast_load(VOICEMAIL_CONFIG);
629 ast_log(LOG_WARNING, "No voicemail configuration\n");
632 if (chan->state != AST_STATE_UP)
634 if (ast_streamfile(chan, "vm-login", chan->language)) {
635 ast_log(LOG_WARNING, "Couldn't stream login file\n");
639 /* Authenticate them and get their mailbox/password */
642 /* Prompt for, and read in the username */
643 if (ast_readstring(chan, username, sizeof(username), 2000, 10000, "#") < 0) {
644 ast_log(LOG_WARNING, "Couldn't read username\n");
647 if (!strlen(username)) {
648 if (option_verbose > 2)
649 ast_verbose(VERBOSE_PREFIX_3 "Username not entered\n");
653 if (ast_streamfile(chan, "vm-password", chan->language)) {
654 ast_log(LOG_WARNING, "Unable to stream password file\n");
657 if (ast_readstring(chan, password, sizeof(password), 2000, 10000, "#") < 0) {
658 ast_log(LOG_WARNING, "Unable to read password\n");
661 copy = ast_variable_retrieve(cfg, NULL, username);
665 if (!strcmp(password,copy))
667 else if (option_verbose > 2)
668 ast_verbose( VERBOSE_PREFIX_3 "Incorrect password '%s' for user '%s'\n", password, username);
670 } else if (option_verbose > 2)
671 ast_verbose( VERBOSE_PREFIX_3 "No such user '%s' in config file\n", username);
673 if (ast_streamfile(chan, "vm-incorrect", chan->language))
675 if (ast_waitstream(chan, ""))
682 oldmessages = lastmsg + 1;
685 newmessages = lastmsg + 1;
687 WAITCMD(play_and_wait(chan, "vm-youhave"));
689 WAITCMD(say_and_wait(chan, newmessages));
690 WAITCMD(play_and_wait(chan, "vm-INBOX"));
691 if (newmessages == 1)
692 WAITCMD(play_and_wait(chan, "vm-message"));
694 WAITCMD(play_and_wait(chan, "vm-messages"));
697 WAITCMD(play_and_wait(chan, "vm-and"));
700 WAITCMD(say_and_wait(chan, oldmessages));
701 WAITCMD(play_and_wait(chan, "vm-Old"));
702 if (oldmessages == 1)
703 WAITCMD(play_and_wait(chan, "vm-message"));
705 WAITCMD(play_and_wait(chan, "vm-messages"));
707 if (!oldmessages && !newmessages) {
708 WAITCMD(play_and_wait(chan, "vm-no"));
709 WAITCMD(play_and_wait(chan, "vm-messages"));
711 if (!newmessages && oldmessages) {
712 /* If we only have old messages start here */
720 WAITCMD(play_and_wait(chan, "vm-onefor"));
721 WAITCMD(play_and_wait(chan, vmbox));
722 WAITCMD(play_and_wait(chan, "vm-messages"));
724 WAITCMD(play_and_wait(chan, "vm-opts"));
727 WAITCMD(play_and_wait(chan, "vm-prev"));
728 WAITCMD(play_and_wait(chan, "vm-repeat"));
729 if (curmsg != lastmsg)
730 WAITCMD(play_and_wait(chan, "vm-next"));
731 if (!deleted[curmsg])
732 WAITCMD(play_and_wait(chan, "vm-delete"));
734 WAITCMD(play_and_wait(chan, "vm-undelete"));
735 WAITCMD(play_and_wait(chan, "vm-toforward"));
736 WAITCMD(play_and_wait(chan, "vm-savemessage"));
738 WAITCMD(play_and_wait(chan, "vm-helpexit"));
739 d = ast_waitfordigit(chan, 6000);
745 play_and_wait(chan, "vm-goodbye");
753 box = play_and_wait(chan, "vm-changeto");
756 while((box < '0') || (box > '9')) {
757 box = get_folder(chan, 0);
766 WAITCMD(play_and_wait(chan, vmbox));
767 WAITCMD(play_and_wait(chan, "vm-messages"));
775 WAITCMD(play_and_wait(chan, "vm-nomore"));
785 WAITCMD(play_and_wait(chan, "vm-youhave"));
786 WAITCMD(play_and_wait(chan, "vm-no"));
787 snprintf(fn, sizeof(fn), "vm-%s", curbox);
788 WAITCMD(play_and_wait(chan, fn));
789 WAITCMD(play_and_wait(chan, "vm-messages"));
793 if (curmsg < lastmsg) {
797 WAITCMD(play_and_wait(chan, "vm-nomore"));
801 deleted[curmsg] = !deleted[curmsg];
803 WAITCMD(play_and_wait(chan, "vm-deleted"));
805 WAITCMD(play_and_wait(chan, "vm-undeleted"));
808 box = play_and_wait(chan, "vm-savefolder");
811 while((box < '1') || (box > '9')) {
812 box = get_folder(chan, 1);
819 ast_log(LOG_DEBUG, "Save to folder: %s (%d)\n", mbox(box), box);
820 if (save_to_folder(curdir, curmsg, username, box))
823 WAITCMD(play_and_wait(chan, "vm-message"));
824 WAITCMD(say_and_wait(chan, curmsg + 1) );
825 WAITCMD(play_and_wait(chan, "vm-savedto"));
826 snprintf(fn, sizeof(fn), "vm-%s", mbox(box));
827 WAITCMD(play_and_wait(chan, fn));
828 WAITCMD(play_and_wait(chan, "vm-messages"));
832 WAITCMD(play_and_wait(chan, "vm-onefor"));
833 WAITCMD(play_and_wait(chan, vmbox));
834 WAITCMD(play_and_wait(chan, "vm-messages"));
835 WAITCMD(play_and_wait(chan, "vm-opts"));
839 play_and_wait(chan, "vm-goodbye");
847 ast_stopstream(chan);
850 LOCAL_USER_REMOVE(u);
854 static int vm_exec(struct ast_channel *chan, void *data)
858 char *ext = (char *)data;
861 ast_log(LOG_WARNING, "vm requires an argument (extension)\n");
869 if (chan->state != AST_STATE_UP)
871 res = leave_voicemail(chan, ext, silent);
872 LOCAL_USER_REMOVE(u);
876 int unload_module(void)
879 STANDARD_HANGUP_LOCALUSERS;
880 res = ast_unregister_application(app);
881 res |= ast_unregister_application(app2);
885 int load_module(void)
888 res = ast_register_application(app, vm_exec, synopsis_vm, descrip_vm);
890 res = ast_register_application(app2, vm_execmain, synopsis_vmain, descrip_vmain);
894 char *description(void)
902 STANDARD_USECOUNT(res);
908 return ASTERISK_GPL_KEY;