#!/usr/bin/perl # # Web based Voicemail for Asterisk # # Copyright (C) 2002, Linux Support Services, Inc. # # Distributed under the terms of the GNU General Public License # # Written by Mark Spencer # # (icky, I know.... if you know better perl please help!) # # use CGI qw/:standard/; use Carp::Heavy; use CGI::Carp qw(fatalsToBrowser); use DBI; $context=""; # Define here your by default context (so you dont need to put voicemail@context in the login @validfolders = ( "INBOX", "Old", "Work", "Family", "Friends", "Cust1", "Cust2", "Cust3", "Cust4", "Cust5" ); %formats = ( "wav" => { name => "Uncompressed WAV", mime => "audio/x-wav", pref => 1 }, "WAV" => { name => "GSM Compressed WAV", mime => "audio/x-wav", pref => 2 }, "gsm" => { name => "Raw GSM Audio", mime => "audio/x-gsm", pref => 3 } ); $astpath = "/_asterisk"; $stdcontainerstart = "
\n"; $footer = "
The Asterisk Open Source PBX Copyright 2004, Digium, Inc."; $stdcontainerend = "
$footer
\n"; sub login_screen() { print header; my ($message) = @_; print <<_EOH; Asterisk Web-Voicemail $stdcontainerstart
Comedian Mail Login
$message
Mailbox:
Password:
$stdcontainerend \n _EOH } sub check_login() { local ($filename, $startcat) = @_; local ($mbox, $context) = split(/\@/, param('mailbox')); local $pass = param('password'); local $category = $startcat; local @fields; local $tmp; local (*VMAIL); if (!$category) { $category = "general"; } if (!$context) { $context = param('context'); } if (!$context) { $context = "default"; } if (!$filename) { $filename = "/etc/asterisk/voicemail.conf"; } # print header; # print "Including

$filename

while in

$category

...\n"; open(VMAIL, "<$filename") || die("Bleh, no $filename"); while() { chomp; if (/include\s\"([^\"]+)\"$/) { ($tmp, $category) = &check_login("/etc/asterisk/$1", $category); if (length($tmp)) { # print "Got '$tmp'\n"; return ($tmp, $category); } } elsif (/\[(.*)\]/) { $category = $1; } elsif ($category eq "general") { if (/([^\s]+)\s*\=\s*(.*)/) { if ($1 eq "dbname") { $dbname = $2; } elsif ($1 eq "dbpass") { $dbpass = $2; } elsif ($1 eq "dbhost") { $dbhost = $2; } elsif ($1 eq "dbuser") { $dbuser = $2; } } if ($dbname and $dbpass and $dbhost and $dbuser) { # db variables are present. Use db for authentication. my $dbh = DBI->connect("DBI:mysql:$dbname:$dbhost",$dbuser,$dbpass); my $sth = $dbh->prepare(qq{select fullname,context from users where mailbox='$mbox' and password='$pass' and context='$context'}); $sth->execute(); if (($fullname, $category) = $sth->fetchrow_array()) {; return ($fullname ? $fullname : "Extension $mbox in $context",$category); } } } elsif (($category ne "general") && ($category ne "zonemessages")) { if (/([^\s]+)\s*\=\>?\s*(.*)/) { @fields = split(/\,\s*/, $2); # print "

Mailbox is $1\n"; if (($mbox eq $1) && (($pass eq $fields[0]) || ("-${pass}" eq $fields[0])) && ($context eq $category)) { return ($fields[1] ? $fields[1] : "Extension $mbox in $context", $category); } } } } close(VMAIL); return ("", $category); } sub validmailbox() { local ($context, $mbox, $filename, $startcat) = @_; local $category = $startcat; local @fields; local (*VMAIL); if (!$context) { $context = param('context'); } if (!$context) { $context = "default"; } if (!$filename) { $filename = "/etc/asterisk/voicemail.conf"; } if (!$category) { $category = "general"; } open(VMAIL, "<$filename") || die("Bleh, no $filename"); while() { chomp; if (/include\s\"([^\"]+)\"$/) { ($tmp, $category) = &validmailbox($mbox, $context, "/etc/asterisk/$1"); if ($tmp) { return ($tmp, $category); } } elsif (/\[(.*)\]/) { $category = $1; } elsif ($category eq "general") { if (/([^\s]+)\s*\=\s*(.*)/) { if ($1 eq "dbname") { $dbname = $2; } elsif ($1 eq "dbpass") { $dbpass = $2; } elsif ($1 eq "dbhost") { $dbhost = $2; } elsif ($1 eq "dbuser") { $dbuser = $2; } } if ($dbname and $dbpass and $dbhost and $dbuser) { # db variables are present. Use db for authentication. my $dbh = DBI->connect("DBI:mysql:$dbname:$dbhost",$dbuser,$dbpass); my $sth = $dbh->prepare(qq{select fullname,context from users where mailbox='$mbox' and password='$pass' and context='$context'}); $sth->execute(); if (($fullname, $category) = $sth->fetchrow_array()) {; return ($fullname ? $fullname : "unknown", $category); } } } elsif (($category ne "general") && ($category ne "zonemessages") && ($category eq $context)) { if (/([^\s]+)\s*\=\>?\s*(.*)/) { @fields = split(/\,\s*/, $2); if (($mbox eq $1) && ($context eq $category)) { return ($fields[2] ? $fields[2] : "unknown", $category); } } } } return ("", $category); } sub mailbox_options() { local($context, $current, $filename, $category) = @_; local (*VMAIL); local $tmp2; local $tmp; if (!$filename) { $filename = "/etc/asterisk/voicemail.conf"; } if (!$category) { $category = "general"; } # print header; # print "Including

$filename

while in

$category

...\n"; open(VMAIL, "<$filename") || die("Bleh, no voicemail.conf"); while() { chomp; s/\;.*$//; if (/include\s\"([^\"]+)\"$/) { ($tmp2, $category) = &mailbox_options($context, $current, "/etc/asterisk/$1", $category); # print "Got '$tmp2'...\n"; $tmp .= $tmp2; } elsif (/\[(.*)\]/) { $category = $1; } elsif ($category eq "general") { if (/([^\s]+)\s*\=\s*(.*)/) { if ($1 eq "dbname") { $dbname = $2; } elsif ($1 eq "dbpass") { $dbpass = $2; } elsif ($1 eq "dbhost") { $dbhost = $2; } elsif ($1 eq "dbuser") { $dbuser = $2; } } if ($dbname and $dbpass and $dbhost and $dbuser) { # db variables are present. Use db for authentication. my $dbh = DBI->connect("DBI:mysql:$dbname:$dbhost",$dbuser,$dbpass); my $sth = $dbh->prepare(qq{select mailbox,fullname,context from users where context='$context' order by mailbox}); $sth->execute(); while (($mailbox, $fullname, $category) = $sth->fetchrow_array()) { $text = $mailbox; if ($fullname) { $text .= " (".$fullname.")"; } if ($mailbox eq $current) { $tmp .= "\n"; } else { $tmp .= "\n"; } } return ($tmp, $category); } } elsif (($category ne "general") && ($category ne "zonemessages")) { if (/([^\s]+)\s*\=\>?\s*(.*)/) { @fields = split(/\,\s*/, $2); $text = "$1"; if ($fields[1]) { $text .= " ($fields[1])"; } if ($1 eq $current) { $tmp .= "\n"; } else { $tmp .= "\n"; } } } } close(VMAIL); return ($tmp, $category); } sub mailbox_list() { local ($name, $context, $current) = @_; local $tmp; local $text; local $tmp; local $opts; if (!$context) { $context = "default"; } $tmp = "\n"; } sub msgcount() { my ($context, $mailbox, $folder) = @_; my $path = "/var/spool/asterisk/voicemail/$context/$mailbox/$folder"; if (opendir(DIR, $path)) { my @msgs = grep(/^msg....\.txt$/, readdir(DIR)); closedir(DIR); return sprintf "%d", $#msgs + 1; } return "0"; } sub msgcountstr() { my ($context, $mailbox, $folder) = @_; my $count = &msgcount($context, $mailbox, $folder); if ($count > 1) { "$count messages"; } elsif ($count > 0) { "$count message"; } else { "no messages"; } } sub messages() { my ($context, $mailbox, $folder) = @_; my $path = "/var/spool/asterisk/voicemail/$context/$mailbox/$folder"; if (opendir(DIR, $path)) { my @msgs = sort grep(/^msg....\.txt$/, readdir(DIR)); closedir(DIR); return map { s/^msg(....)\.txt$/$1/; $_ } @msgs; } return (); } sub getcookie() { my ($var) = @_; return cookie($var); } sub makecookie() { my ($format) = @_; cookie(-name => "format", -value =>["$format"], -expires=>"+1y"); } sub getfields() { my ($context, $mailbox, $folder, $msg) = @_; my $fields; if (open(MSG, ") { s/\#.*$//g; if (/^(\w+)\s*\=\s*(.*)$/) { $fields->{$1} = $2; } } close(MSG); $fields->{'msgid'} = $msg; } else { print "
Unable to open '$msg' in '$mailbox', '$folder'\n"; } $fields; } sub message_prefs() { my ($nextaction, $msgid) = @_; my $folder = param('folder'); my $mbox = param('mailbox'); my $context = param('context'); my $passwd = param('password'); my $format = param('format'); if (!$format) { $format = &getcookie('format'); } print header; print <<_EOH; Asterisk Web-Voicemail: Preferences $stdcontainerstart
_EOH foreach $fmt (sort { $formats{$a}->{'pref'} <=> $formats{$b}->{'pref'} } keys %formats) { my $clicked = "checked" if $fmt eq $format; print "\n"; } print <<_EOH;
Web Voicemail Preferences
Preferred Audio Format:
 $formats{$fmt}->{name}
$stdcontainerend \n _EOH } sub message_play() { my ($message, $msgid) = @_; my $folder = param('folder'); my ($mbox, $context) = split(/\@/, param('mailbox')); my $passwd = param('password'); my $format = param('format'); my $fields; if (!$context) { $context = param('context'); } if (!$context) { $context = "default"; } my $folders = &folder_list('newfolder', $context, $mbox, $folder); my $mailboxes = &mailbox_list('forwardto', $context, $mbox); if (!$format) { $format = &getcookie('format'); } if (!$format) { &message_prefs("play", $msgid); } else { print header(-cookie => &makecookie($format)); $fields = &getfields($context, $mbox, $folder, $msgid); if (!$fields) { print "
Bah!\n"; return; } my $duration = $fields->{'duration'}; if ($duration) { $duration = sprintf "%d:%02d", $duration/60, $duration % 60; } else { $duration = "Unknown"; } print <<_EOH; Asterisk Web-Voicemail: $folder Message $msgid $stdcontainerstart _EOH print <<_EOH;
$folder Message $msgid
\n \n \n \n \n \n \n \n
$folder $msgid
Message:$msgid
Mailbox:$mbox\@$context
Folder:$folder
From:$fields->{callerid}
Duration:$duration
Original Date:$fields->{origdate}
Original Mailbox:$fields->{origmailbox}
Caller Channel:$fields->{callerchan}
      $mailboxes  $folders 
$stdcontainerend \n _EOH } } sub message_audio() { my ($forcedownload) = @_; my $folder = param('folder'); my $msgid = param('msgid'); my $mailbox = param('mailbox'); my $context = param('context'); my $format = param('format'); if (!$format) { $format = &getcookie('format'); } my $path = "/var/spool/asterisk/voicemail/$context/$mailbox/$folder/msg${msgid}.$format"; $msgid =~ /^\d\d\d\d$/ || die("Msgid Liar ($msgid)!"); grep(/^${format}$/, keys %formats) || die("Format Liar ($format)!"); # Mailbox and folder are already verified if (open(AUDIO, "<$path")) { $size = -s $path; $|=1; if ($forcedownload) { print header(-type=>$formats{$format}->{'mime'}, -Content_length => $size, -attachment => "msg${msgid}.$format"); } else { print header(-type=>$formats{$format}->{'mime'}, -Content_length => $size); } while(($amt = sysread(AUDIO, $data, 4096)) > 0) { syswrite(STDOUT, $data, $amt); } close(AUDIO); } else { die("Hrm, can't seem to open $path\n"); } } sub message_index() { my ($folder, $message) = @_; my ($mbox, $context) = split(/\@/, param('mailbox')); my $passwd = param('password'); my $message2; my $msgcount; my $hasmsg; my $newmessages, $oldmessages; my $format = param('format'); if (!$format) { $format = &getcookie('format'); } if (!$context) { $context = param('context'); } if (!$context) { $context = "default"; } if ($folder) { $msgcount = &msgcountstr($context, $mbox, $folder); $message2 = "   Folder '$folder' has " . &msgcountstr($context, $mbox, $folder); } else { $newmessages = &msgcount($context, $mbox, "INBOX"); $oldmessages = &msgcount($context, $mbox, "Old"); if (($newmessages > 0) || ($oldmessages < 1)) { $folder = "INBOX"; } else { $folder = "Old"; } $message2 = "You have"; if ($newmessages > 0) { $message2 .= " $newmessages NEW"; if ($oldmessages > 0) { $message2 .= "and $oldmessages OLD"; if ($oldmessages != 1) { $message2 .= " messages."; } else { $message2 .= "message."; } } else { if ($newmessages != 1) { $message2 .= " messages."; } else { $message2 .= " message."; } } } else { if ($oldmessages > 0) { $message2 .= " $oldmessages OLD"; if ($oldmessages != 1) { $message2 .= " messages."; } else { $message2 .= " message."; } } else { $message2 .= " no messages."; } } } my $folders = &folder_list('newfolder', $context, $mbox, $folder); my $cfolders = &folder_list('changefolder', $context, $mbox, $folder); my $mailboxes = &mailbox_list('forwardto', $context, $mbox); print header(-cookie => &makecookie($format)); print <<_EOH; Asterisk Web-Voicemail: $mbox\@$context $folder $stdcontainerstart
$message
$folder Messages $cfolders
$message2
_EOH print "\n"; print "\n"; foreach $msg (&messages($context, $mbox, $folder)) { $fields = &getfields($context, $mbox, $folder, $msg); $duration = $fields->{'duration'}; if ($duration) { $duration = sprintf "%d:%02d", $duration / 60, $duration % 60; } else { $duration = "Unknown"; } $hasmsg++; print "\n"; } if (!$hasmsg) { print ""; } print <<_EOH;
 Msg From Duration Date 




 $msg$fields->{'callerid'}$duration$fields->{'origdate'}

No messages

  _EOH if ($hasmsg) { print <<_EOH;   $folders  $mailboxes _EOH } print <<_EOH;
$stdcontainerend \n _EOH } sub validfolder() { my ($folder) = @_; return grep(/^$folder$/, @validfolders); } sub folder_list() { my ($name, $context, $mbox, $selected) = @_; my $f; my $count; my $tmp = ""; } sub message_rename() { my ($context, $mbox, $oldfolder, $old, $newfolder, $new) = @_; my $oldfile, $newfile; return if ($old eq $new) && ($oldfolder eq $newfolder); if ($context =~ /^(\w+)$/) { $context = $1; } else { die("Invalid Context
\n"); } if ($mbox =~ /^(\w+)$/) { $mbox = $1; } else { die ("Invalid mailbox
\n"); } if ($oldfolder =~ /^(\w+)$/) { $oldfolder = $1; } else { die("Invalid old folder
\n"); } if ($newfolder =~ /^(\w+)$/) { $newfolder = $1; } else { die("Invalid new folder ($newfolder)
\n"); } if ($old =~ /^(\d\d\d\d)$/) { $old = $1; } else { die("Invalid old Message
\n"); } if ($new =~ /^(\d\d\d\d)$/) { $new = $1; } else { die("Invalid old Message
\n"); } my $path = "/var/spool/asterisk/voicemail/$context/$mbox/$newfolder"; mkdir $path, 0770; my $path = "/var/spool/asterisk/voicemail/$context/$mbox/$oldfolder"; opendir(DIR, $path) || die("Unable to open directory\n"); my @files = grep /^msg${old}\.\w+$/, readdir(DIR); closedir(DIR); foreach $oldfile (@files) { my $tmp = $oldfile; if ($tmp =~ /^(msg${old}.\w+)$/) { $tmp = $1; $oldfile = $path . "/$tmp"; $tmp =~ s/msg${old}/msg${new}/; $newfile = "/var/spool/asterisk/voicemail/$context/$mbox/$newfolder/$tmp"; # print "Renaming $oldfile to $newfile
\n"; rename($oldfile, $newfile); } } } sub file_copy() { my ($orig, $new) = @_; my $res; my $data; open(IN, "<$orig") || die("Unable to open '$orig'\n"); open(OUT, ">$new") || DIE("Unable to open '$new'\n"); while(($res = sysread(IN, $data, 4096)) > 0) { syswrite(OUT, $data, $res); } close(OUT); close(IN); } sub message_copy() { my ($context, $mbox, $newmbox, $oldfolder, $old, $new) = @_; my $oldfile, $newfile; return if ($mbox eq $newmbox); if ($mbox =~ /^(\w+)$/) { $mbox = $1; } else { die ("Invalid mailbox
\n"); } if ($newmbox =~ /^(\w+)$/) { $newmbox = $1; } else { die ("Invalid new mailbox
\n"); } if ($oldfolder =~ /^(\w+)$/) { $oldfolder = $1; } else { die("Invalid old folder
\n"); } if ($old =~ /^(\d\d\d\d)$/) { $old = $1; } else { die("Invalid old Message
\n"); } if ($new =~ /^(\d\d\d\d)$/) { $new = $1; } else { die("Invalid old Message
\n"); } my $path = "/var/spool/asterisk/voicemail/$context/$newmbox"; mkdir $path, 0770; my $path = "/var/spool/asterisk/voicemail/$context/$newmbox/INBOX"; mkdir $path, 0770; my $path = "/var/spool/asterisk/voicemail/$context/$mbox/$oldfolder"; opendir(DIR, $path) || die("Unable to open directory\n"); my @files = grep /^msg${old}\.\w+$/, readdir(DIR); closedir(DIR); foreach $oldfile (@files) { my $tmp = $oldfile; if ($tmp =~ /^(msg${old}.\w+)$/) { $tmp = $1; $oldfile = $path . "/$tmp"; $tmp =~ s/msg${old}/msg${new}/; $newfile = "/var/spool/asterisk/voicemail/$context/$newmbox/INBOX/$tmp"; # print "Copying $oldfile to $newfile
\n"; &file_copy($oldfile, $newfile); } } } sub message_delete() { my ($context, $mbox, $folder, $msg) = @_; if ($mbox =~ /^(\w+)$/) { $mbox = $1; } else { die ("Invalid mailbox
\n"); } if ($context =~ /^(\w+)$/) { $context = $1; } else { die ("Invalid context
\n"); } if ($folder =~ /^(\w+)$/) { $folder = $1; } else { die("Invalid folder
\n"); } if ($msg =~ /^(\d\d\d\d)$/) { $msg = $1; } else { die("Invalid Message
\n"); } my $path = "/var/spool/asterisk/voicemail/$context/$mbox/$folder"; opendir(DIR, $path) || die("Unable to open directory\n"); my @files = grep /^msg${msg}\.\w+$/, readdir(DIR); closedir(DIR); foreach $oldfile (@files) { if ($oldfile =~ /^(msg${msg}.\w+)$/) { $oldfile = $path . "/$1"; # print "Deleting $oldfile
\n"; unlink($oldfile); } } } sub message_forward() { my ($toindex, @msgs) = @_; my $folder = param('folder'); my ($mbox, $context) = split(/\@/, param('mailbox')); my $newmbox = param('forwardto'); my $msg; my $msgcount; if (!$context) { $context = param('context'); } if (!$context) { $context = "default"; } $newmbox =~ s/(\w+)(\s+.*)?$/$1/; if (!&validmailbox($context, $newmbox)) { die("Bah! Not a valid mailbox '$newmbox'\n"); return ""; } $msgcount = &msgcount($context, $newmbox, "INBOX"); my $txt; if ($newmbox ne $mbox) { # print header; foreach $msg (@msgs) { # print "Forwarding $msg from $mbox to $newmbox
\n"; &message_copy($context, $mbox, $newmbox, $folder, $msg, sprintf "%04d", $msgcount); $msgcount++; } $txt = "Forwarded messages " . join(', ', @msgs) . "to $newmbox"; } else { $txt = "Can't forward messages to yourself!\n"; } if ($toindex) { &message_index($folder, $txt); } else { &message_play($txt, $msgs[0]); } } sub message_delete_or_move() { my ($toindex, $del, @msgs) = @_; my $txt; my $path; my $y, $x; my $folder = param('folder'); my $newfolder = param('newfolder') unless $del; $newfolder =~ s/^(\w+)\s+.*$/$1/; my ($mbox, $context) = split(/\@/, param('mailbox')); if (!$context) { $context = param('context'); } if (!$context) { $context = "default"; } my $passwd = param('password'); my $msgcount = &msgcount($context, $mbox, $folder); my $omsgcount = &msgcount($context, $mbox, $newfolder) if $newfolder; # print header; if ($newfolder ne $folder) { $y = 0; for ($x=0;$x<$msgcount;$x++) { my $msg = sprintf "%04d", $x; my $newmsg = sprintf "%04d", $y; if (grep(/^$msg$/, @msgs)) { if ($newfolder) { &message_rename($context, $mbox, $folder, $msg, $newfolder, sprintf "%04d", $omsgcount); $omsgcount++; } else { &message_delete($context, $mbox, $folder, $msg); } } else { &message_rename($context, $mbox, $folder, $msg, $folder, $newmsg); $y++; } } if ($del) { $txt = "Deleted messages " . join (', ', @msgs); } else { $txt = "Moved messages " . join (', ', @msgs) . " to $newfolder"; } } else { $txt = "Can't move a message to the same folder they're in already"; } # Not as many messages now $msgcount--; if ($toindex || ($msgs[0] >= $msgcount)) { &message_index($folder, $txt); } else { &message_play($txt, $msgs[0]); } } if (param()) { my $folder = param('folder'); my $changefolder = param('changefolder'); $changefolder =~ s/(\w+)\s+.*$/$1/; my $newfolder = param('newfolder'); $newfolder =~ s/^(\w+)\s+.*$/$1/; if ($newfolder && !&validfolder($newfolder)) { print header; die("Bah! new folder '$newfolder' isn't a folder."); } $action = param('action'); $msgid = param('msgid'); if (!$action) { my ($tmp) = grep /^play\d\d\d\d\.x$/, param; if ($tmp =~ /^play(\d\d\d\d)/) { $msgid = $1; $action = "play"; } else { print header; print "No message?
\n"; return; } } @msgs = param('msgselect'); @msgs = ($msgid) unless @msgs; { ($mailbox) = &check_login(); if (length($mailbox)) { if ($action eq 'login') { &message_index($folder, "Welcome, $mailbox"); } elsif (($action eq 'refresh') || ($action eq 'index')) { &message_index($folder, "Welcome, $mailbox"); } elsif ($action eq 'change to ->') { if (&validfolder($changefolder)) { $folder = $changefolder; &message_index($folder, "Welcome, $mailbox"); } else { die("Bah! Not a valid change to folder '$changefolder'\n"); } } elsif ($action eq 'play') { &message_play("$mailbox $folder $msgid", $msgid); } elsif ($action eq 'preferences') { &message_prefs("refresh", $msgid); } elsif ($action eq 'download') { &message_audio(1); } elsif ($action eq 'play ') { &message_audio(0); } elsif ($action eq 'audio') { &message_audio(0); } elsif ($action eq 'delete') { &message_delete_or_move(1, 1, @msgs); } elsif ($action eq 'delete ') { &message_delete_or_move(0, 1, @msgs); } elsif ($action eq 'forward to ->') { &message_forward(1, @msgs); } elsif ($action eq 'forward to -> ') { &message_forward(0, @msgs); } elsif ($action eq 'save to ->') { &message_delete_or_move(1, 0, @msgs); } elsif ($action eq 'save to -> ') { &message_delete_or_move(0, 0, @msgs); } elsif ($action eq 'logout') { &login_screen("Logged out!\n"); } } else { sleep(1); &login_screen("Login Incorrect!\n"); } } } else { &login_screen("\ "); }