3 # Web based Voicemail for Asterisk
5 # Copyright (C) 2002, Linux Support Services, Inc.
7 # Distributed under the terms of the GNU General Public License
9 # Written by Mark Spencer <markster@linux-support.net>
11 # (icky, I know.... if you know better perl please help!)
14 use CGI qw/:standard/;
16 use CGI::Carp qw(fatalsToBrowser);
18 @validfolders = ( "INBOX", "Old", "Work", "Family", "Friends", "Cust1", "Cust2", "Cust3", "Cust4", "Cust5" );
22 name => "Uncompressed WAV",
23 mime => "audio/x-wav",
27 name => "GSM Compressed WAV",
28 mime => "audio/x-wav",
32 name => "Raw GSM Audio",
33 mime => "audio/x-gsm",
38 $astpath = "/_asterisk";
40 $stdcontainerstart = "<table align=center width=600><tr><td>\n";
41 $footer = "<hr><font size=-1><a href=\"http://www.asterisk.org\">The Asterisk Open Source PBX</a> Copyright 2002, <a href=\"http://www.digium.com\">Digium, Inc.</a></a>";
42 $stdcontainerend = "</td></tr><tr><td align=right>$footer</td></tr></table>\n";
49 <TITLE>Asterisk Web-Voicemail</TITLE>
50 <BODY BGCOLOR="white">
53 <input type=hidden name="action" value="login">
55 <tr><td valign=top align=center rowspan=6><img align=center src="$astpath/animlogo.gif"></td></tr>
56 <tr><td align=center colspan=2><font size=+2>Comedian Mail Login</font></td></tr>
57 <tr><td align=center colspan=2><font size=+1>$message</font></td></tr>
58 <tr><td>Mailbox:</td><td><input type=text name="mailbox"></td></tr>
59 <tr><td>Password:</td><td><input type=password name="password"></td></tr>
60 <tr><td align=right colspan=2><input value="Login" type=submit></td></tr>
71 local ($filename, $startcat) = @_;
72 local ($mbox, $context) = split(/\@/, param('mailbox'));
73 local $pass = param('password');
74 local $category = $startcat;
79 $category = "general";
82 $context = param('context');
88 $filename = "/etc/asterisk/voicemail.conf";
91 # print "Including <h2>$filename</h2> while in <h2>$category</h2>...\n";
92 open(VMAIL, "<$filename") || die("Bleh, no $filename");
95 if (/include\s\"([^\"]+)\"$/) {
96 ($tmp, $category) = &check_login("/etc/asterisk/$1", $category);
98 # print "Got '$tmp'\n";
99 return ($tmp, $category);
101 } elsif (/\[(.*)\]/) {
103 } elsif ($category ne "general") {
104 if (/([^\s]+)\s*\=\>?\s*(.*)/) {
105 @fields = split(/\,\s*/, $2);
106 # print "<p>Mailbox is $1\n";
107 if (($mbox eq $1) && ($pass eq $fields[0]) && ($context eq $category)) {
108 return ($fields[1] ? $fields[1] : "Extension $mbox in $context", $category);
114 return ("", $category);
119 local ($context, $mbox, $filename, $startcat) = @_;
120 local $category = $startcat;
124 $context = param('context');
127 $context = "default";
130 $filename = "/etc/asterisk/voicemail.conf";
133 $category = "general";
135 open(VMAIL, "<$filename") || die("Bleh, no $filename");
138 if (/include\s\"([^\"]+)\"$/) {
139 ($tmp, $category) = &validmailbox($mbox, $context, "/etc/asterisk/$1");
141 return ($tmp, $category);
143 } elsif (/\[(.*)\]/) {
145 } elsif (($category ne "general") && ($category eq $context)) {
146 if (/([^\s]+)\s*\=\>?\s*(.*)/) {
147 @fields = split(/\,\s*/, $2);
148 if (($mbox eq $1) && ($context eq $category)) {
149 return ($fields[2] ? $fields[2] : "unknown", $category);
154 return ("", $category);
157 sub mailbox_options()
159 local($context, $current, $filename, $category) = @_;
164 $filename = "/etc/asterisk/voicemail.conf";
167 $category = "general";
170 # print "Including <h2>$filename</h2> while in <h2>$category</h2>...\n";
171 open(VMAIL, "<$filename") || die("Bleh, no voicemail.conf");
175 if (/include\s\"([^\"]+)\"$/) {
176 ($tmp2, $category) = &mailbox_options($context, $current, "/etc/asterisk/$1", $category);
177 # print "Got '$tmp2'...\n";
179 } elsif (/\[(.*)\]/) {
181 } elsif ($category ne "general") {
182 if (/([^\s]+)\s*\=\>?\s*(.*)/) {
183 @fields = split(/\,\s*/, $2);
186 $text .= " ($fields[1])";
188 if ($1 eq $current) {
189 $tmp .= "<OPTION SELECTED>$text</OPTION>\n";
191 $tmp .= "<OPTION>$text</OPTION>\n";
198 return ($tmp, $category);
203 local ($name, $context, $current) = @_;
209 $context = "default";
211 $tmp = "<SELECT name=\"$name\">\n";
212 ($opts) = &mailbox_options($context, $current);
214 $tmp .= "</SELECT>\n";
220 my ($context, $mailbox, $folder) = @_;
221 my $path = "/var/spool/asterisk/voicemail/$context/$mailbox/$folder";
222 if (opendir(DIR, $path)) {
223 my @msgs = grep(/^msg....\.txt$/, readdir(DIR));
225 return sprintf "%d", $#msgs + 1;
232 my ($context, $mailbox, $folder) = @_;
233 my $count = &msgcount($context, $mailbox, $folder);
236 } elsif ($count > 0) {
244 my ($context, $mailbox, $folder) = @_;
245 my $path = "/var/spool/asterisk/voicemail/$context/$mailbox/$folder";
246 if (opendir(DIR, $path)) {
247 my @msgs = sort grep(/^msg....\.txt$/, readdir(DIR));
249 return map { s/^msg(....)\.txt$/$1/; $_ } @msgs;
263 cookie(-name => "format", -value =>["$format"], -expires=>"+1y");
268 my ($context, $mailbox, $folder, $msg) = @_;
270 if (open(MSG, "</var/spool/asterisk/voicemail/$context/$mailbox/$folder/msg${msg}.txt")) {
273 if (/^(\w+)\s*\=\s*(.*)$/) {
278 $fields->{'msgid'} = $msg;
279 } else { print "<BR>Unable to open '$msg' in '$mailbox', '$folder'\n<B>"; }
285 my ($nextaction, $msgid) = @_;
286 my $folder = param('folder');
287 my $mbox = param('mailbox');
288 my $passwd = param('password');
289 my $format = param('format');
291 $format = &getcookie('format');
296 <TITLE>Asterisk Web-Voicemail: Preferences</TITLE>
297 <BODY BGCOLOR="white">
300 <table width=100% align=center>
301 <tr><td align=right colspan=3><font size=+2>Web Voicemail Preferences</font></td></tr>
302 <tr><td align=left><font size=+1>Preferred Audio Format:</font></td><td colspan=2></td></tr>
305 foreach $fmt (sort { $formats{$a}->{'pref'} <=> $formats{$b}->{'pref'} } keys %formats) {
306 my $clicked = "checked" if $fmt eq $format;
307 print "<tr><td></td><td align=left><input type=radio name=\"format\" $clicked value=\"$fmt\"></td><td width=100%> $formats{$fmt}->{name}</td></tr>\n";
311 <tr><td align=right colspan=3><input type=submit value="save settings..."></td></tr>
313 <input type=hidden name="action" value="$nextaction">
314 <input type=hidden name="folder" value="$folder">
315 <input type=hidden name="mailbox" value="$mbox">
316 <input type=hidden name="context" value="$context">
317 <input type=hidden name="password" value="$passwd">
318 <input type=hidden name="msgid" value="$msgid">
327 my ($message, $msgid) = @_;
328 my $folder = param('folder');
329 my ($mbox, $context) = split(/\@/, param('mailbox'));
330 my $passwd = param('password');
331 my $format = param('format');
335 $context = param('context');
338 $context = "default";
341 my $folders = &folder_list('newfolder', $context, $mbox, $folder);
342 my $mailboxes = &mailbox_list('forwardto', $context, $mbox);
344 $format = &getcookie('format');
347 &message_prefs("play", $msgid);
349 print header(-cookie => &makecookie($format));
350 $fields = &getfields($context, $mbox, $folder, $msgid);
355 my $duration = $fields->{'duration'};
357 $duration = sprintf "%d:%02d", $duration/60, $duration % 60;
359 $duration = "<i>Unknown</i>";
363 <TITLE>Asterisk Web-Voicemail: $folder Message $msgid</TITLE>
364 <BODY BGCOLOR="white">
367 <table width=100% align=center>
368 <tr><td align=right colspan=3><font size=+1>$folder Message $msgid</font></td></tr>
372 <tr><td align=center colspan=3>
374 <tr><td colspan=2 align=center><font size=+1>$folder <b>$msgid</b></font></td></tr>
375 <tr><td><b>Message:</b></td><td>$msgid</td></tr>\n
376 <tr><td><b>Mailbox:</b></td><td>$mbox</td></tr>\n
377 <tr><td><b>Folder:</b></td><td>$folder</td></tr>\n
378 <tr><td><b>From:</b></td><td>$fields->{callerid}</td></tr>\n
379 <tr><td><b>Duration:</b></td><td>$duration</td></tr>\n
380 <tr><td><b>Original Date:</b></td><td>$fields->{origdate}</td></tr>\n
381 <tr><td><b>Original Mailbox:</b></td><td>$fields->{origmailbox}</td></tr>\n
382 <tr><td><b>Caller Channel:</b></td><td>$fields->{callerchan}</td></tr>\n
383 <tr><td align=center colspan=2>
384 <input name="action" type=submit value="index">
385 <input name="action" type=submit value="delete ">
386 <input name="action" type=submit value="forward to -> ">
388 <input name="action" type=submit value="save to ->">
390 <input name="action" type=submit value="play ">
391 <input name="action" type=submit value="download">
393 <tr><td colspan=2 align=center>
394 <embed width=400 height=40 src="vmail.cgi?action=audio&folder=$folder&mailbox=$mbox&password=$passwd&msgid=$msgid&format=$format&dontcasheme=$$.$format" autostart=yes loop=false></embed>
398 <input type=hidden name="folder" value="$folder">
399 <input type=hidden name="mailbox" value="$mbox">
400 <input type=hidden name="context" value="$context">
401 <input type=hidden name="password" value="$passwd">
402 <input type=hidden name="msgid" value="$msgid">
411 my ($forcedownload) = @_;
412 my $folder = param('folder');
413 my $msgid = param('msgid');
414 my $mailbox = param('mailbox');
415 my $format = param('format');
417 $format = &getcookie('format');
419 my $path = "/var/spool/asterisk/vm/$mailbox/$folder/msg${msgid}.$format";
421 $msgid =~ /^\d\d\d\d$/ || die("Msgid Liar ($msgid)!");
422 grep(/^${format}$/, keys %formats) || die("Format Liar ($format)!");
424 # Mailbox and folder are already verified
425 if (open(AUDIO, "<$path")) {
428 if ($forcedownload) {
429 print header(-type=>$formats{$format}->{'mime'}, -Content_length => $size, -attachment => "msg${msgid}.$format");
431 print header(-type=>$formats{$format}->{'mime'}, -Content_length => $size);
434 while(($amt = sysread(AUDIO, $data, 4096)) > 0) {
435 syswrite(STDOUT, $data, $amt);
439 die("Hrm, can't seem to open $path\n");
445 my ($folder, $message) = @_;
446 my ($mbox, $context) = split(/\@/, param('mailbox'));
447 my $passwd = param('password');
451 my $newmessages, $oldmessages;
452 my $format = param('format');
454 $format = &getcookie('format');
457 $context = param('context');
460 $context = "default";
463 $msgcount = &msgcountstr($context, $mbox, $folder);
464 $message2 = " Folder '$folder' has " . &msgcountstr($context, $mbox, $folder);
466 $newmessages = &msgcount($context, $mbox, "INBOX");
467 $oldmessages = &msgcount($context, $mbox, "Old");
468 if (($newmessages > 0) || ($oldmessages < 1)) {
473 $message2 = "You have";
474 if ($newmessages > 0) {
475 $message2 .= " <b>$newmessages</b> NEW";
476 if ($oldmessages > 0) {
477 $message2 .= "and <b>$oldmessages</b> OLD";
478 if ($oldmessages != 1) {
479 $message2 .= " messages.";
481 $message2 .= "message.";
484 if ($newmessages != 1) {
485 $message2 .= " messages.";
487 $message2 .= " message.";
491 if ($oldmessages > 0) {
492 $message2 .= " <b>$oldmessages</b> OLD";
493 if ($oldmessages != 1) {
494 $message2 .= " messages.";
496 $message2 .= " message.";
499 $message2 .= " <b>no</b> messages.";
504 my $folders = &folder_list('newfolder', $context, $mbox, $folder);
505 my $cfolders = &folder_list('changefolder', $context, $mbox, $folder);
506 my $mailboxes = &mailbox_list('forwardto', $context, $mbox);
507 print header(-cookie => &makecookie($format));
510 <TITLE>Asterisk Web-Voicemail: $mbox $folder</TITLE>
511 <BODY BGCOLOR="white">
514 <table width=100% align=center>
515 <tr><td align=center colspan=2><font size=+2><I>$message</I></font></td></tr>
516 <tr><td align=right colspan=2><font size=+1><b>$folder</b> Messages</font> <input type=submit name="action" value="change to ->">$cfolders</td></tr>
517 <tr><td align=left colspan=2><font size=+1>$message2</font></td></tr>
519 <table width=100% align=center cellpadding=0 cellspacing=0>
522 print "<tr><td> Msg</td><td> From</td><td> Duration</td><td> Date</td><td> </td></tr>\n";
523 print "<tr><td><hr></td><td><hr></td><td><hr></td><td><hr></td><td></td></tr>\n";
524 foreach $msg (&messages($context, $mbox, $folder)) {
526 $fields = &getfields($context, $mbox, $folder, $msg);
527 $duration = $fields->{'duration'};
529 $duration = sprintf "%d:%02d", $duration / 60, $duration % 60;
531 $duration = "<i>Unknown</i>";
534 print "<tr><td><input type=checkbox name=\"msgselect\" value=\"$msg\"> <b>$msg</b></td><td>$fields->{'callerid'}</td><td>$duration</td><td>$fields->{'origdate'}</td><td><input name='play$msg' alt=\"Play message $msg\" border=0 type=image align=left src=\"$astpath/play.gif\"></td></tr>\n";
538 print "<tr><td colspan=4 align=center><P><b><i>No messages</i></b><P></td></tr>";
543 <table width=100% align=center>
544 <tr><td align=right colspan=2>
545 <input type="submit" name="action" value="refresh">
550 <input type="submit" name="action" value="delete">
551 <input type="submit" name="action" value="save to ->">
553 <input type="submit" name="action" value="forward to ->">
560 <tr><td align=right colspan=2>
561 <input type="submit" name="action" value="preferences">
562 <input type="submit" name="action" value="logout">
565 <input type=hidden name="folder" value="$folder">
566 <input type=hidden name="mailbox" value="$mbox">
567 <input type=hidden name="context" value="$context">
568 <input type=hidden name="password" value="$passwd">
578 return grep(/^$folder$/, @validfolders);
583 my ($name, $context, $mbox, $selected) = @_;
586 my $tmp = "<SELECT name=\"$name\">\n";
587 foreach $f (@validfolders) {
588 $count = &msgcount($context, $mbox, $f);
589 if ($f eq $selected) {
590 $tmp .= "<OPTION SELECTED>$f ($count)</OPTION>\n";
592 $tmp .= "<OPTION>$f ($count)</OPTION>\n";
600 my ($context, $mbox, $oldfolder, $old, $newfolder, $new) = @_;
601 my $oldfile, $newfile;
602 return if ($old eq $new) && ($oldfolder eq $newfolder);
604 if ($context =~ /^(\w+)$/) {
607 die("Invalid Context<BR>\n");
610 if ($mbox =~ /^(\w+)$/) {
613 die ("Invalid mailbox<BR>\n");
616 if ($oldfolder =~ /^(\w+)$/) {
619 die("Invalid old folder<BR>\n");
622 if ($newfolder =~ /^(\w+)$/) {
625 die("Invalid new folder ($newfolder)<BR>\n");
628 if ($old =~ /^(\d\d\d\d)$/) {
631 die("Invalid old Message<BR>\n");
634 if ($new =~ /^(\d\d\d\d)$/) {
637 die("Invalid old Message<BR>\n");
640 my $path = "/var/spool/asterisk/voicemail/$context/$mbox/$newfolder";
642 my $path = "/var/spool/asterisk/voicemail/$context/$mbox/$oldfolder";
643 opendir(DIR, $path) || die("Unable to open directory\n");
644 my @files = grep /^msg${old}\.\w+$/, readdir(DIR);
646 foreach $oldfile (@files) {
648 if ($tmp =~ /^(msg${old}.\w+)$/) {
650 $oldfile = $path . "/$tmp";
651 $tmp =~ s/msg${old}/msg${new}/;
652 $newfile = "/var/spool/asterisk/voicemail/$context/$mbox/$newfolder/$tmp";
653 # print "Renaming $oldfile to $newfile<BR>\n";
654 rename($oldfile, $newfile);
661 my ($orig, $new) = @_;
664 open(IN, "<$orig") || die("Unable to open '$orig'\n");
665 open(OUT, ">$new") || DIE("Unable to open '$new'\n");
666 while(($res = sysread(IN, $data, 4096)) > 0) {
667 syswrite(OUT, $data, $res);
675 my ($mbox, $newmbox, $oldfolder, $old, $new) = @_;
676 my $oldfile, $newfile;
677 return if ($mbox eq $newmbox);
679 if ($mbox =~ /^(\w+)$/) {
682 die ("Invalid mailbox<BR>\n");
685 if ($newmbox =~ /^(\w+)$/) {
688 die ("Invalid new mailbox<BR>\n");
691 if ($oldfolder =~ /^(\w+)$/) {
694 die("Invalid old folder<BR>\n");
697 if ($old =~ /^(\d\d\d\d)$/) {
700 die("Invalid old Message<BR>\n");
703 if ($new =~ /^(\d\d\d\d)$/) {
706 die("Invalid old Message<BR>\n");
709 my $path = "/var/spool/asterisk/vm/$newmbox";
711 my $path = "/var/spool/asterisk/vm/$newmbox/INBOX";
713 my $path = "/var/spool/asterisk/vm/$mbox/$oldfolder";
714 opendir(DIR, $path) || die("Unable to open directory\n");
715 my @files = grep /^msg${old}\.\w+$/, readdir(DIR);
717 foreach $oldfile (@files) {
719 if ($tmp =~ /^(msg${old}.\w+)$/) {
721 $oldfile = $path . "/$tmp";
722 $tmp =~ s/msg${old}/msg${new}/;
723 $newfile = "/var/spool/asterisk/vm/$newmbox/INBOX/$tmp";
724 # print "Copying $oldfile to $newfile<BR>\n";
725 &file_copy($oldfile, $newfile);
732 my ($context, $mbox, $folder, $msg) = @_;
733 if ($mbox =~ /^(\w+)$/) {
736 die ("Invalid mailbox<BR>\n");
738 if ($context =~ /^(\w+)$/) {
741 die ("Invalid context<BR>\n");
743 if ($folder =~ /^(\w+)$/) {
746 die("Invalid folder<BR>\n");
748 if ($msg =~ /^(\d\d\d\d)$/) {
751 die("Invalid Message<BR>\n");
753 my $path = "/var/spool/asterisk/voicemail/$context/$mbox/$folder";
754 opendir(DIR, $path) || die("Unable to open directory\n");
755 my @files = grep /^msg${msg}\.\w+$/, readdir(DIR);
757 foreach $oldfile (@files) {
758 if ($oldfile =~ /^(msg${msg}.\w+)$/) {
759 $oldfile = $path . "/$1";
760 # print "Deleting $oldfile<BR>\n";
766 sub message_forward()
768 my ($toindex, @msgs) = @_;
769 my $folder = param('folder');
770 my ($mbox, $context) = split(/\@/, param('mailbox'));
771 my $newmbox = param('forwardto');
775 $context = param('context');
778 $context = "default";
780 $newmbox =~ s/(\w+)(\s+.*)?$/$1/;
781 if (!&validmailbox($context, $newmbox)) {
782 die("Bah! Not a valid mailbox '$newmbox'\n");
785 $msgcount = &msgcount($context, $newmbox, "INBOX");
787 if ($newmbox ne $mbox) {
789 foreach $msg (@msgs) {
790 # print "Forwarding $msg from $mbox to $newmbox<BR>\n";
791 &message_copy($mbox, $newmbox, $folder, $msg, sprintf "%04d", $msgcount);
794 $txt = "Forwarded messages " . join(', ', @msgs) . "to $newmbox";
796 $txt = "Can't forward messages to yourself!\n";
799 &message_index($folder, $txt);
801 &message_play($txt, $msgs[0]);
805 sub message_delete_or_move()
807 my ($toindex, $del, @msgs) = @_;
811 my $folder = param('folder');
812 my $newfolder = param('newfolder') unless $del;
813 $newfolder =~ s/^(\w+)\s+.*$/$1/;
814 my ($mbox, $context) = split(/\@/, param('mailbox'));
816 $context = param('context');
819 $context = "default";
821 my $passwd = param('password');
822 my $msgcount = &msgcount($context, $mbox, $folder);
823 my $omsgcount = &msgcount($context, $mbox, $newfolder) if $newfolder;
825 if ($newfolder ne $folder) {
827 for ($x=0;$x<$msgcount;$x++) {
828 my $msg = sprintf "%04d", $x;
829 my $newmsg = sprintf "%04d", $y;
830 if (grep(/^$msg$/, @msgs)) {
832 &message_rename($context, $mbox, $folder, $msg, $newfolder, sprintf "%04d", $omsgcount);
835 &message_delete($context, $mbox, $folder, $msg);
838 &message_rename($context, $mbox, $folder, $msg, $folder, $newmsg);
843 $txt = "Deleted messages " . join (', ', @msgs);
845 $txt = "Moved messages " . join (', ', @msgs) . " to $newfolder";
848 $txt = "Can't move a message to the same folder they're in already";
850 # Not as many messages now
852 if ($toindex || ($msgs[0] >= $msgcount)) {
853 &message_index($folder, $txt);
855 &message_play($txt, $msgs[0]);
860 my $folder = param('folder');
861 my $changefolder = param('changefolder');
862 $changefolder =~ s/(\w+)\s+.*$/$1/;
864 my $newfolder = param('newfolder');
865 $newfolder =~ s/^(\w+)\s+.*$/$1/;
866 if ($newfolder && !&validfolder($newfolder)) {
868 die("Bah! new folder '$newfolder' isn't a folder.");
870 $action = param('action');
871 $msgid = param('msgid');
873 my ($tmp) = grep /^play\d\d\d\d\.x$/, param;
874 if ($tmp =~ /^play(\d\d\d\d)/) {
879 print "No message?<BR>\n";
883 @msgs = param('msgselect');
884 @msgs = ($msgid) unless @msgs;
886 ($mailbox) = &check_login();
887 if (length($mailbox)) {
888 if ($action eq 'login') {
889 &message_index($folder, "Welcome, $mailbox");
890 } elsif (($action eq 'refresh') || ($action eq 'index')) {
891 &message_index($folder, "Welcome, $mailbox");
892 } elsif ($action eq 'change to ->') {
893 if (&validfolder($changefolder)) {
894 $folder = $changefolder;
895 &message_index($folder, "Welcome, $mailbox");
897 die("Bah! Not a valid change to folder '$changefolder'\n");
899 } elsif ($action eq 'play') {
900 &message_play("$mailbox $folder $msgid", $msgid);
901 } elsif ($action eq 'preferences') {
902 &message_prefs("refresh", $msgid);
903 } elsif ($action eq 'download') {
905 } elsif ($action eq 'play ') {
907 } elsif ($action eq 'audio') {
909 } elsif ($action eq 'delete') {
910 &message_delete_or_move(1, 1, @msgs);
911 } elsif ($action eq 'delete ') {
912 &message_delete_or_move(0, 1, @msgs);
913 } elsif ($action eq 'forward to ->') {
914 &message_forward(1, @msgs);
915 } elsif ($action eq 'forward to -> ') {
916 &message_forward(0, @msgs);
917 } elsif ($action eq 'save to ->') {
918 &message_delete_or_move(1, 0, @msgs);
919 } elsif ($action eq 'save to -> ') {
920 &message_delete_or_move(0, 0, @msgs);
921 } elsif ($action eq 'logout') {
922 &login_screen("Logged out!\n");
926 &login_screen("Login Incorrect!\n");
930 &login_screen("\ ");