Commit ba26ef8e authored by Ted Trask's avatar Ted Trask

Initial cut - but pretty close to complete.

parents
APP_NAME=freeswitch-vmail
PACKAGE=acf-$(APP_NAME)
VERSION=0.0.1
APP_DIST=\
vmail* \
template* \
LIB_DIST=authenticator-freeswitch-vmail.lua
EXTRA_DIST=README Makefile config.mk
DISTFILES=$(APP_DIST) $(LIB_DIST) $(EXTRA_DIST)
TAR=tar
P=$(PACKAGE)-$(VERSION)
tarball=$(P).tar.bz2
install_dir=$(DESTDIR)/$(appdir)/$(APP_NAME)
all:
clean:
rm -rf $(tarball) $(P)
dist: $(tarball)
install:
mkdir -p "$(install_dir)"
cp -a $(APP_DIST) "$(install_dir)"
mkdir -p "$(acflibdir)"
cp -a $(LIB_DIST) "$(acflibdir)"
$(tarball): $(DISTFILES)
rm -rf $(P)
mkdir -p $(P)
cp -a $(DISTFILES) $(P)
$(TAR) -jcf $@ $(P)
rm -rf $(P)
# target that creates a tar package, unpacks is and install from package
dist-install: $(tarball)
$(TAR) -jxf $(tarball)
$(MAKE) -C $(P) install DESTDIR=$(DESTDIR)
rm -rf $(P)
include config.mk
.PHONY: all clean dist install dist-install
acf-freeswitch-vmail is a web interface that works with Freeswitch to implement a single-domain voicemail server. Since it uses mod_xml_curl and mod_event_socket, these must be configured properly in Freeswitch for acf-freeswitch-mail to work.
Be sure to load both modules in autoload_configs/modules.conf.xml
The following content can be used in autoload_configs/xml_curl.conf.xml:
<configuration name="xml_curl.conf" description="cURL XML Gateway">
<bindings>
<binding name="voicemaildialplan">
<param name="gateway-url" value="https://127.0.0.1/cgi-bin/acf/freeswitch-vmail/vmail/processdialplanxml" bindings="dialplan"/>
</binding>
<binding name="voicemaildirectory">
<param name="gateway-url" value="https://127.0.0.1/cgi-bin/acf/freeswitch-vmail/vmail/processdirectoryxml" bindings="directory"/>
</binding>
</bindings>
</configuration>
-- Copy of authenticator-plaintext, plus added authentication from voicemail DB
module (..., package.seeall)
require("md5")
a = require("authenticator-plaintext")
list_fields = function(self, tabl)
result = a.list_fields(self, tabl)
return result
end
read_field = function(self, tabl, field)
result = a.read_field(self, tabl, field)
if tabl == authenticator.usertable and field == "" then
-- authenticator is reading all users
local vmcontroller = self:new("freeswitch-vmail/vmail")
local users = vmcontroller:listusers()
for i,val in ipairs(users.value) do
local settings = vmcontroller.model.get_usersettings(val.username)
local string = md5.sumhexa(settings.value["vm-password"].value)..":Voicemail User:/freeswitch-vmail/vmail/USER"
result[#result+1] = { id=settings.value.username.value, entry=string }
end
vmcontroller:destroy()
end
return result
end
delete_field = function(self, tabl, field)
result = a.delete_field(self, tabl, field)
return result
end
write_entry = function(self, tabl, field, id, entry)
result = a.write_entry(self, tabl, field, id, entry)
return result
end
read_entry = function(self, tabl, field, id)
result = a.read_entry(self, tabl, field, id)
return result
end
delete_entry = function (self, tabl, field, id)
result = a.delete_entry(self, tabl, field, id)
return result
end
prefix=/usr
datadir=${prefix}/share
sysconfdir=${prefix}/etc
localstatedir=${prefix}/var
acfdir=${datadir}/acf
wwwdir=${acfdir}/www
cgibindir=${acfdir}/cgi-bin
appdir=${acfdir}/app
acflibdir=${acfdir}/lib
sessionsdir=${localstatedir}/lib/acf/sessions
<% local viewtable, viewlibrary, pageinfo, session = ... %>
<% if viewtable and not viewtable.errtxt then %>
Content-Type: Content Type: text/xml
<document type="freeswitch/xml">
<section name="dialplan">
<context name="default">
<extension name="Voicemail">
<condition expression="(.*)" field="destination_number">
<action application="answer" />
<action application="sleep" data="1000" />
<action application="voicemail" data="default <%= viewtable.value.domain.value %> $1" />
</condition>
</extension>
</context>
</section>
</document>
<% end %>
<% local viewtable, viewlibrary, pageinfo, session = ... %>
<% if viewtable and not viewtable.errtxt then %>
Content-Type: Content Type: text/xml
<document type="freeswitch/xml">
<section name="directory">
<domain name="<%= viewtable.value.domain.value %>">
<params>
<param name="dial-string" value="{presence_id=${dialed_user}@${dialed_domain}}${sofia_contact(${dialed_user}@${dialed_domain})}" />
</params>
<groups>
<group name="default">
<users>
<user id="<%= viewtable.value.username.value %>">
<params>
<param name="vm-enable" value="true>" />
<%
local ignore = {username=true, fullname=true, domain=true, ["vm-password-confirm"]=true }
for name,val in pairs(viewtable.value) do
if not ignore[name] then %>
<param name="<%= name %>" value="<%= tostring(val.value) %>" />
<% end %>
<% end %>
</params>
</user>
</users>
</group>
</groups>
</domain>
</section>
</document>
<% end %>
module (..., package.seeall)
require("controllerfunctions")
default_action = "listmessages"
listusers = function( self )
return self.model.list_users()
end
editusers = function( self )
return self.model.list_users()
end
deleteuser = function( self )
return self:redirect_to_referrer(self.model.delete_user(self.clientdata.username))
end
listmessages = function( self )
return self.model.list_messages(self.clientdata.username)
end
listmymessages = function( self )
return self.model.list_messages(self.sessiondata.userinfo.userid)
end
downloadmessage = function( self )
self.conf.viewtype = "stream"
return self.model.get_message(self.clientdata.message)
end
downloadmymessage = function( self )
self.conf.viewtype = "stream"
return self.model.get_message(self.clientdata.message, self.sessiondata.userinfo.userid)
end
deletemessage = function( self )
return self:redirect_to_referrer(self.model.delete_message(self.clientdata.message))
end
deletemymessage = function( self )
return self:redirect_to_referrer(self.model.delete_message(self.clientdata.message, self.sessiondata.userinfo.userid))
end
forwardmessage = function( self )
return self:redirect_to_referrer(self.model.forward_message(self.clientdata.message, self.clientdata.newuser))
end
forwardmymessage = function( self )
return self:redirect_to_referrer(self.model.forward_message(self.clientdata.message, self.clientdata.newuser, self.sessiondata.userinfo.userid))
end
emailmessage = function( self )
return self:redirect_to_referrer(self.model.email_message(self.clientdata.message, self.clientdata.address))
end
emailmymessage = function( self )
return self:redirect_to_referrer(self.model.email_message(self.clientdata.message, self.clientdata.address, self.sessiondata.userinfo.userid))
end
editusersettings = function( self )
return controllerfunctions.handle_form(self, function() return self.model.get_usersettings(self.clientdata.username) end, self.model.update_usersettings, self.clientdata, "Save", "Edit Settings", "Settings Saved")
end
editmyusersettings = function( self )
return controllerfunctions.handle_form(self, function() return self.model.get_usersettings(self.sessiondata.userinfo.userid) end, self.model.update_usersettings, self.clientdata, "Save", "Edit Settings", "Settings Saved")
end
createuser = function( self )
return controllerfunctions.handle_form(self, function() return self.model.get_usersettings() end, self.model.create_usersettings, self.clientdata, "Create", "Create User", "User Created")
end
processdialplanxml = function( self )
self.conf.viewtype = "xml"
return self.model.process_dialplan_xml_request(self.clientdata)
end
processdirectoryxml = function( self )
self.conf.viewtype = "xml"
return self.model.process_directory_xml_request(self.clientdata)
end
editconfig = function( self )
return controllerfunctions.handle_form(self, self.model.get_config, self.model.update_config, self.clientdata, "Save", "Update Config", "Config Saved")
end
vmail-editusersettings-html.lsp
\ No newline at end of file
../form-html.lsp
\ No newline at end of file
vmail-editusersettings-html.lsp
\ No newline at end of file
vmail-listusers-html.lsp
\ No newline at end of file
<% local form, viewlibrary, page_info = ...
require("viewfunctions")
%>
<H1>Settings for <%= html.html_escape(form.value.fullname.value) %> (<%= html.html_escape(form.value.username.value) %>)</H1>
<%
form.action = page_info.script .. page_info.prefix .. page_info.controller .. "/" .. page_info.action
if page_info.action ~= "createuser" then
form.value.username.readonly = true
end
form.value["vm-password"].type = "password"
form.value["vm-password-confirm"].type = "password"
local order = {"username", "fullname", "vm-password", "vm-password-confirm", "vm-mailto", "vm-email-all-messages", "vm-attach-file", "vm-keep-local-after-email", "vm-notify-mailto", "vm-notify-email-all-messages", "vm-say-caller-id", "vm-say-envelope"}
displayform(form, order)
%>
<% local view, viewlibrary, page_info, session = ...
require("viewfunctions")
%>
<% -- Pregenerate the list of users
if viewlibrary.check_permission("listusers") and (viewlibrary.check_permission("forwardmessage") or viewlibrary.check_permission("forwardmymessage")) then
local users = viewlibrary.dispatch_component("listusers", nil, true)
options = {}
for i,u in ipairs(users.value) do
if u.username ~= session.userinfo.userid then
options[#options+1] = '<option value="'..html.html_escape(u.username)..'">'..html.html_escape(u.fullname)..' ('..html.html_escape(u.username)..')</option>'
end
end
options = table.concat(options)
end
%>
<script type="text/javascript" src="<%= html.html_escape(page_info.wwwprefix) %>/js/jquery-latest.js"></script>
<script type="text/javascript" src="<%= html.html_escape(page_info.wwwprefix) %>/js/jquery.tablesorter.js"></script>
<script type="text/javascript">
function PlayMessage() {
$(".temporaryplayer").remove();
$(this).parent().parent().after("<tr class='temporaryplayer'><td colspan='8'><embed width='100%' height='25px' marginheight='0' marginwidth='0' frameborder='0' scrolling='no' autostart='false' autoplay='false' loop='false' src='" + $(this).attr("href") + "'></embed></TD></TR>");
return false;
}
function HandleMulti() {
var messages = [];
$(".multicheck:checked").each(function(){
messages[messages.length] = this.value;
});
$(this).parent().find("[name='message']").attr("value", messages.join(","));
}
$(document).ready(function() {
$("#list").tablesorter({headers: {0:{sorter: false}, 1:{sorter: false}}});
$("#list").bind("sortStart",function() {
$(".temporaryplayer").remove();
});
$(".playmessage").click(PlayMessage);
<% if viewlibrary.check_permission("deletemessage") or viewlibrary.check_permission("deletemymessage") then %>
$("#multidelete").click(HandleMulti);
<% end %>
<% if viewlibrary.check_permission("forwardmessage") or viewlibrary.check_permission("forwardmymessage") then %>
$("#multiforward").click(HandleMulti);
<% end %>
<% if viewlibrary.check_permission("emailmessage") or viewlibrary.check_permission("emailmymessage") then %>
$("#multiemail").click(HandleMulti);
<% end %>
});
</script>
<% displaycommandresults({"deletemessage", "deletemymessage", "forwardmessage", "forwardmymessage", "emailmessage", "emailmymessage"}, session) %>
<h1>Messages</h1>
<DL>
<TABLE><TR><TD>
<% if viewlibrary.check_permission("deletemessage") or viewlibrary.check_permission("deletemymessage") then %>
<form id="multidelete" action="<%= html.html_escape(page_info.script..page_info.prefix..page_info.controller) %>/
<% if viewlibrary.check_permission("deletemessage") then io.write("deletemessage") else io.write("deletemymessage") end %>
" method="POST">
<input class="hidden" type="hidden" name="redir" value="<%= html.html_escape(page_info.orig_action) %>" >
<input class="hidden" type="hidden" name="message" value="" >
<input class="submit" type="submit" value="Delete">
</form>
<% end %>
<% if viewlibrary.check_permission("forwardmessage") or viewlibrary.check_permission("forwardmymessage") then %>
<form id="multiforward" action="<%= html.html_escape(page_info.script .. page_info.prefix .. page_info.controller) %>/
<% if viewlibrary.check_permission("forwardmessage") then io.write("forwardmessage") else io.write("forwardmymessage") end %>
" method="POST">
<input class="hidden" type="hidden" name="redir" value="<%= html.html_escape(page_info.orig_action) %>" >
<input class="hidden" type="hidden" name="message" value="" >
<select name="newuser"><%= (options or "") %></select>
<input class="submit" type="submit" value="Forward">
</form>
<% end %>
<% if viewlibrary.check_permission("emailmessage") or viewlibrary.check_permission("emailmymessage") then %>
<form id="multiemail" action="<%= html.html_escape(page_info.script .. page_info.prefix .. page_info.controller) %>/
<% if viewlibrary.check_permission("emailmessage") then io.write("emailmessage") else io.write("emailmymessage") end %>
" method="POST">
<input class="hidden" type="hidden" name="redir" value="<%= html.html_escape(page_info.orig_action) %>" >
<input class="hidden" type="hidden" name="message" value="" >
<input class="text" type="text" name="address" value="" >
<input class="submit" type="submit" value="E-mail">
</form>
<% end %>
</TD></TR></TABLE>
<TABLE id="list" class="tablesorter"><THEAD>
<TR style="background:#eee;font-weight:bold;">
<TH></TH>
<TH>Action</TH>
<TH>Date</TH>
<TH>Time</TH>
<TH>Caller ID</TH>
<TH>Priority</TH>
<TH>Orig Mailbox</Th>
<TH>Duration</TH>
</TR>
</THEAD><TBODY>
<% for k,v in ipairs( view.value ) do %>
<TR>
<TD><input type=checkbox class="multicheck" value="<%= html.html_escape(v.uuid) %>"></TD>
<TD>
<% if viewlibrary.check_permission("downloadmessage") then %>
<%= html.link{value=page_info.script..page_info.prefix..page_info.controller.."/downloadmessage?message="..v.uuid, label="Download "} %>
<%= html.link{value=page_info.script..page_info.prefix..page_info.controller.."/downloadmessage?message="..v.uuid, class="playmessage", label="Play "} %>
<% elseif viewlibrary.check_permission("downloadmymessage") then %>
<%= html.link{value=page_info.script..page_info.prefix..page_info.controller.."/downloadmymessage?message="..v.uuid, label="Download "} %>
<%= html.link{value=page_info.script..page_info.prefix..page_info.controller.."/downloadmymessage?message="..v.uuid, class="playmessage", label="Play "} %>
<% end %>
</TD>
<TD><%= html.html_escape(os.date("%x", v.created_epoch)) %></TD>
<TD><%= html.html_escape(os.date("%X", v.created_epoch)) %></TD>
<TD><%= html.html_escape(v.cid_number) %></TD>
<TD><%= html.html_escape(v.read_flags) %></TD>
<TD><%= html.html_escape(v.username) %></TD>
<TD><%= html.html_escape(v.message_len) %></TD>
</TR>
<% end %>
</TBODY>
</TABLE>
<% if view.errtxt then %>
<p class="error"><%= html.html_escape(view.errtxt) %></p>
<% end %>
<% if #view.value == 0 then %>
<p>No messages found</p>
<% end %>
</DL>
vmail-listmessages-html.lsp
\ No newline at end of file
<% local view, viewlibrary, page_info, session = ...
require("viewfunctions")
%>
<script type="text/javascript" src="<%= html.html_escape(page_info.wwwprefix) %>/js/jquery-latest.js"></script>
<script type="text/javascript" src="<%= html.html_escape(page_info.wwwprefix) %>/js/jquery.tablesorter.js"></script>
<script type="text/javascript">
$(document).ready(function() {
$("#list").tablesorter({headers: {0:{sorter: false}}});
});
</script>
<% displaycommandresults({"createuser", "deleteuser", "editusersettings"}, session) %>
<h1>Messages</h1>
<DL><TABLE id="list" class="tablesorter"><THEAD>
<TR style="background:#eee;font-weight:bold;">
<TH>Action</TH>
<TH>Extension</TH>
<TH>Full Name</TH>
</TR>
</THEAD><TBODY>
<% for k,v in ipairs( view.value ) do %>
<TR>
<TD>
<% if viewlibrary.check_permission("editusersettings") then %>
<%= html.link{value=page_info.script..page_info.prefix..page_info.controller.."/editusersettings?username="..v.username, label="Edit "} %>
<% end %>
<% if viewlibrary.check_permission("deleteuser") then %>
<%= html.link{value=page_info.script..page_info.prefix..page_info.controller.."/deleteuser?username="..v.username, label="Delete "} %>
<% end %>
</TD>
<TD><%= html.html_escape(v.username) %></TD>
<TD><%= html.html_escape(v.fullname) %></TD>
</TR>
<% end %>
</TBODY>
</TABLE>
<% if view.errtxt then %>
<p class="error"><%= html.html_escape(view.errtxt) %></p>
<% end %>
<% if #view.value == 0 then %>
<p>No users found</p>
<% end %>
<% if viewlibrary and viewlibrary.dispatch_component and viewlibrary.check_permission("createuser") then %>
<H2>Create New User</H2>
<form action="<%= html.html_escape(page_info.script .. page_info.prefix .. page_info.controller) %>/createuser" method="POST">
<input class="hidden" type="hidden" name="redir" value="<%= html.html_escape(page_info.orig_action) %>" >
<dl><dt></dt><dd><input class="submit" type="submit" value="Create"></dd></dl>
</form>
<% end %>
</DL>
module (..., package.seeall)
-- Load libraries
require("modelfunctions")
require("posix")
require("fs")
require("format")
require("validator")
require("luasql.sqlite3")
require("session")
-- Set variables
local configfile = "/etc/freeswitchvmail.conf"
local configcontent = fs.read_file(configfile) or ""
local config = format.parse_ini_file(configcontent, "") or {}
config.database = config.database or "/var/lib/freeswitch/db/voicemail_default.db"
config.domain = config.domain or "voicemail"
config.event_socket_ip = config.event_socket_ip or "127.0.0.1"
config.event_socket_port = config.event_socket_port or "8021"
config.event_socket_password = config.event_socket_password or "ClueCon"
local env
local con
local voicemail_users_creation_script = {
"CREATE TABLE voicemail_users (username text)",
}
local voicemail_values_creation_script = {
"CREATE TABLE voicemail_values (username text, name text, value text)",
}
local voicemail_params_creation_script = {
"CREATE TABLE voicemail_params (name text primary key, type text, label text, descr text, value text)",
"INSERT INTO voicemail_params VALUES('username', 'text', 'Extension', '', '')",
"INSERT INTO voicemail_params VALUES('fullname', 'text', 'Full User Name', '', '')",
"INSERT INTO voicemail_params VALUES('vm-password', 'text', 'Voicemail Password', '', '')",
"INSERT INTO voicemail_params VALUES('vm-password-confirm', 'text', 'Enter again to confirm', '', '')",
"INSERT INTO voicemail_params VALUES('vm-mailto', 'text', 'Email Address', 'Email a notification, including audio file if enabled', '')",
"INSERT INTO voicemail_params VALUES('vm-email-all-messages', 'boolean', 'Email Enable', '', 'false')",
"INSERT INTO voicemail_params VALUES('vm-attach-file', 'boolean', 'Attach voicemail to email', 'Option to attach audio file to email', 'false')",
"INSERT INTO voicemail_params VALUES('vm-keep-local-after-email', 'boolean', 'Keep voicemail after emailed', 'When disabled the message will be deleted from the voicemailbox after the notification email is sent. This allows receiving voicemail via email alone, rather than having the voicemail available from the Web interface or by telephone. CAUTION: Attach voicemail to email must be enabled, OTHERWISE YOUR MESSAGES WILL BE LOST FOREVER.', 'true')",
"INSERT INTO voicemail_params VALUES('vm-notify-mailto', 'text', 'Pager Email Address', 'Email a short notification', '')",
"INSERT INTO voicemail_params VALUES('vm-notify-email-all-messages', 'boolean', 'Pager Email Enable', '', 'false')",
}
local voicemail_prefs_creation_script = "CREATE TABLE voicemail_prefs (username VARCHAR(255), domain VARCHAR(255), name_path VARCHAR(255), greeting_path VARCHAR(255), password VARCHAR(255))"
-- ################################################################################
-- LOCAL FUNCTIONS
local function escape_quotes(str)
return string.gsub(str or "", "'", "'\\''")
end
local function voicemail_inject(user, domain, sound_file, cid_num, cid_name)
local cmd = "echo -e 'auth "..escape_quotes(config.event_socket_password).."\n\n"
cmd = cmd.."api voicemail_inject "..escape_quotes(user).."@"..escape_quotes(domain).." "..escape_quotes(sound_file).." "..escape_quotes(cid_num).." "..string.gsub(escape_quotes(cid_name), " ", "%%20")
cmd = cmd.."\n\nexit\n\n' | nc "..format.escapespecialcharacters(config.event_socket_ip).." "..format.escapespecialcharacters(config.event_socket_port).." 2>&1"
local f = io.popen( cmd )
local result = f:read("*a") or ""
f:close()
return result
end
local function assert (v, m)
if not v then
m = m or "Assertion failed!"
error(m, 0)
end
return v, m
end
-- Escape special characters in sql statements
local escape = function(sql)
sql = sql or ""
sql = string.gsub(sql, "'", "''")
return string.gsub(sql, "\\", "\\\\")
end
local databaseconnect = function()
if not con then
-- create environment object
env = assert (luasql.sqlite3())
-- connect to data source
con = assert (env:connect(config.database))
return true
end
return false
end
local databasedisconnect = function()
if env then
env:close()
env = nil
end
if con then
con:close()
con = nil
end
end
local runscript = function(script)
for i,scr in ipairs(script) do
logevent(scr)
assert( con:execute(scr) )
end
end
local checktable = function(table)
local success = false
local errtxt
local res, err = pcall(function()
local sql = "SELECT * FROM "..table.." LIMIT 1"
local cur = assert (con:execute(sql))
cur:close()
success = true
end)
if not res and err then
errtxt = err
end
return success, errtxt
end
local getselectresponse = function(sql)
local retval = {}
local cur = assert (con:execute(sql))
local row = cur:fetch ({}, "a")
while row do
local tmp = {}
for name,val in pairs(row) do
tmp[name] = val
end
retval[#retval + 1] = tmp
row = cur:fetch (row, "a")
end
cur:close()
return retval
end
local generatewhereclause = function(username, message)
local sql = ""
local where = {}
if username and username ~= "" then
where[#where+1