From ba26ef8ecef31ae3bd145b276994f76e5f633ebd Mon Sep 17 00:00:00 2001 From: Ted Trask Date: Thu, 9 Sep 2010 14:34:52 +0000 Subject: Initial cut - but pretty close to complete. --- Makefile | 48 +++ README | 16 + authenticator-freeswitch-vmail.lua | 46 +++ config.mk | 10 + template-processdialplanxml-xml.lsp | 18 + template-processdirectoryxml-xml.lsp | 32 ++ vmail-controller.lua | 85 +++++ vmail-createuser-html.lsp | 1 + vmail-editconfig-html.lsp | 1 + vmail-editmyusersettings-html.lsp | 1 + vmail-editusers-html.lsp | 1 + vmail-editusersettings-html.lsp | 15 + vmail-listmessages-html.lsp | 129 +++++++ vmail-listmymessages-html.lsp | 1 + vmail-listusers-html.lsp | 55 +++ vmail-model.lua | 638 +++++++++++++++++++++++++++++++++++ vmail.menu | 7 + vmail.roles | 5 + 18 files changed, 1109 insertions(+) create mode 100644 Makefile create mode 100644 README create mode 100644 authenticator-freeswitch-vmail.lua create mode 100644 config.mk create mode 100644 template-processdialplanxml-xml.lsp create mode 100644 template-processdirectoryxml-xml.lsp create mode 100644 vmail-controller.lua create mode 120000 vmail-createuser-html.lsp create mode 120000 vmail-editconfig-html.lsp create mode 120000 vmail-editmyusersettings-html.lsp create mode 120000 vmail-editusers-html.lsp create mode 100644 vmail-editusersettings-html.lsp create mode 100644 vmail-listmessages-html.lsp create mode 120000 vmail-listmymessages-html.lsp create mode 100644 vmail-listusers-html.lsp create mode 100644 vmail-model.lua create mode 100644 vmail.menu create mode 100644 vmail.roles diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..c3b23a5 --- /dev/null +++ b/Makefile @@ -0,0 +1,48 @@ +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 diff --git a/README b/README new file mode 100644 index 0000000..bd07f7e --- /dev/null +++ b/README @@ -0,0 +1,16 @@ +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: + + + + + + + + + + + diff --git a/authenticator-freeswitch-vmail.lua b/authenticator-freeswitch-vmail.lua new file mode 100644 index 0000000..5824dc7 --- /dev/null +++ b/authenticator-freeswitch-vmail.lua @@ -0,0 +1,46 @@ +-- 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 diff --git a/config.mk b/config.mk new file mode 100644 index 0000000..45f4d21 --- /dev/null +++ b/config.mk @@ -0,0 +1,10 @@ +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 diff --git a/template-processdialplanxml-xml.lsp b/template-processdialplanxml-xml.lsp new file mode 100644 index 0000000..547d8b4 --- /dev/null +++ b/template-processdialplanxml-xml.lsp @@ -0,0 +1,18 @@ +<% local viewtable, viewlibrary, pageinfo, session = ... %> +<% if viewtable and not viewtable.errtxt then %> +Content-Type: Content Type: text/xml + + +
+ + + + + + + + + +
+
+<% end %> diff --git a/template-processdirectoryxml-xml.lsp b/template-processdirectoryxml-xml.lsp new file mode 100644 index 0000000..b8f9719 --- /dev/null +++ b/template-processdirectoryxml-xml.lsp @@ -0,0 +1,32 @@ +<% local viewtable, viewlibrary, pageinfo, session = ... %> +<% if viewtable and not viewtable.errtxt then %> +Content-Type: Content Type: text/xml + + +
+ + + + + + + + + + + <% + 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 %> + + <% end %> + <% end %> + + + + + + +
+
+<% end %> diff --git a/vmail-controller.lua b/vmail-controller.lua new file mode 100644 index 0000000..7b947fb --- /dev/null +++ b/vmail-controller.lua @@ -0,0 +1,85 @@ +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 diff --git a/vmail-createuser-html.lsp b/vmail-createuser-html.lsp new file mode 120000 index 0000000..29fea1f --- /dev/null +++ b/vmail-createuser-html.lsp @@ -0,0 +1 @@ +vmail-editusersettings-html.lsp \ No newline at end of file diff --git a/vmail-editconfig-html.lsp b/vmail-editconfig-html.lsp new file mode 120000 index 0000000..4b6b762 --- /dev/null +++ b/vmail-editconfig-html.lsp @@ -0,0 +1 @@ +../form-html.lsp \ No newline at end of file diff --git a/vmail-editmyusersettings-html.lsp b/vmail-editmyusersettings-html.lsp new file mode 120000 index 0000000..29fea1f --- /dev/null +++ b/vmail-editmyusersettings-html.lsp @@ -0,0 +1 @@ +vmail-editusersettings-html.lsp \ No newline at end of file diff --git a/vmail-editusers-html.lsp b/vmail-editusers-html.lsp new file mode 120000 index 0000000..215a6d3 --- /dev/null +++ b/vmail-editusers-html.lsp @@ -0,0 +1 @@ +vmail-listusers-html.lsp \ No newline at end of file diff --git a/vmail-editusersettings-html.lsp b/vmail-editusersettings-html.lsp new file mode 100644 index 0000000..77cb5bf --- /dev/null +++ b/vmail-editusersettings-html.lsp @@ -0,0 +1,15 @@ +<% local form, viewlibrary, page_info = ... +require("viewfunctions") +%> + +

Settings for <%= html.html_escape(form.value.fullname.value) %> (<%= html.html_escape(form.value.username.value) %>)

+<% + 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) +%> diff --git a/vmail-listmessages-html.lsp b/vmail-listmessages-html.lsp new file mode 100644 index 0000000..853d05c --- /dev/null +++ b/vmail-listmessages-html.lsp @@ -0,0 +1,129 @@ +<% 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] = '' + end + end + options = table.concat(options) +end +%> + + + + + +<% displaycommandresults({"deletemessage", "deletemymessage", "forwardmessage", "forwardmymessage", "emailmessage", "emailmymessage"}, session) %> + +

Messages

+
+
+<% if viewlibrary.check_permission("deletemessage") or viewlibrary.check_permission("deletemymessage") then %> +
+ " method="POST"> + + + +
+<% end %> +<% if viewlibrary.check_permission("forwardmessage") or viewlibrary.check_permission("forwardmymessage") then %> +
+ " method="POST"> + + + + +
+<% end %> +<% if viewlibrary.check_permission("emailmessage") or viewlibrary.check_permission("emailmymessage") then %> +
+ " method="POST"> + + + + +
+<% end %> +
+ + + + + + + + + + + + + +<% for k,v in ipairs( view.value ) do %> + + + + + + + + + + +<% end %> + +
ActionDateTimeCaller IDPriorityOrig MailboxDuration
+ <% 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 %> + <%= html.html_escape(os.date("%x", v.created_epoch)) %><%= html.html_escape(os.date("%X", v.created_epoch)) %><%= html.html_escape(v.cid_number) %><%= html.html_escape(v.read_flags) %><%= html.html_escape(v.username) %><%= html.html_escape(v.message_len) %>
+ +<% if view.errtxt then %> +

<%= html.html_escape(view.errtxt) %>

+<% end %> +<% if #view.value == 0 then %> +

No messages found

+<% end %> + +
diff --git a/vmail-listmymessages-html.lsp b/vmail-listmymessages-html.lsp new file mode 120000 index 0000000..a18ada3 --- /dev/null +++ b/vmail-listmymessages-html.lsp @@ -0,0 +1 @@ +vmail-listmessages-html.lsp \ No newline at end of file diff --git a/vmail-listusers-html.lsp b/vmail-listusers-html.lsp new file mode 100644 index 0000000..3445ae2 --- /dev/null +++ b/vmail-listusers-html.lsp @@ -0,0 +1,55 @@ +<% local view, viewlibrary, page_info, session = ... +require("viewfunctions") +%> + + + + + +<% displaycommandresults({"createuser", "deleteuser", "editusersettings"}, session) %> + +

Messages

+
+ + + + + + +<% for k,v in ipairs( view.value ) do %> + + + + + +<% end %> + +
ActionExtensionFull Name
+ <% 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 %> + <%= html.html_escape(v.username) %><%= html.html_escape(v.fullname) %>
+ +<% if view.errtxt then %> +

<%= html.html_escape(view.errtxt) %>

+<% end %> +<% if #view.value == 0 then %> +

No users found

+<% end %> + +<% if viewlibrary and viewlibrary.dispatch_component and viewlibrary.check_permission("createuser") then %> +

Create New User

+
+ +
+
+<% end %> + +
diff --git a/vmail-model.lua b/vmail-model.lua new file mode 100644 index 0000000..886e910 --- /dev/null +++ b/vmail-model.lua @@ -0,0 +1,638 @@ +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] = "username = '"..escape(username).."'" + end + if message and type(message) == "string" and message ~= "" then + where[#where+1] = "uuid = '"..escape(message).."'" + elseif message and type(message) == "table" and #message > 0 then + local where2 = {} + for i,m in ipairs(message) do + where2[#where2+1] = "uuid = '"..escape(m).."'" + end + where[#where+1] = "(" .. table.concat(where2, " OR ") .. ")" + end + if #where > 0 then + sql = " WHERE " .. table.concat(where, " AND ") + end + return sql +end + +-- These funtions access the new voicemail tables added for ACF + +local listusers = function(username) + if not checktable("voicemail_users") then runscript(voicemail_users_creation_script) end + local sql = "SELECT * FROM voicemail_users" .. generatewhereclause(username).." ORDER BY username" + return getselectresponse(sql) +end + +local validuser = function(username) + return username and (username ~= "") and (#listusers(username) > 0) +end + +local getuserparams = function(username) + local retval = {} + if not checktable("voicemail_params") then runscript(voicemail_params_creation_script) end + local sql = "SELECT * FROM voicemail_params" + local params = getselectresponse(sql) + for i,parm in ipairs(params) do + if parm.name then + retval[parm.name] = {} + for n,v in pairs(parm) do + retval[parm.name][n] = v + end + if retval[parm.name].type == "boolean" then + retval[parm.name].value = (retval[parm.name].value == "true") + end + end + end + if retval.username and username then retval.username.value = username end + if validuser(username) then + -- Get password from voicemail_prefs (don't fail for missing table) + if checktable("voicemail_prefs") then + local sql = "SELECT password FROM voicemail_prefs"..generatewhereclause(username) + local password = getselectresponse(sql) + if retval["vm-password"] and password[1] then + retval["vm-password"].value = password[1].password + end + end + + -- Get other parameters from voicemail_values + if not checktable("voicemail_values") then runscript(voicemail_values_creation_script) end + sql = "SELECT * FROM voicemail_values"..generatewhereclause(username) + local params = getselectresponse(sql) + for i,param in ipairs(params) do + if param.name and retval[param.name] and param.value then + if retval[param.name].type == "boolean" then + param.value = (param.value == "true") + end + retval[param.name].value = param.value + end + end + end + return retval +end + +local setuserparams = function(userparams) + if not userparams.username or not userparams.username.value or not validuser(userparams.username.value) then + return false, "Invalid User" + end + local success = true + if not checktable("voicemail_params") then runscript(voicemail_params_creation_script) end + local sql = "SELECT * FROM voicemail_params" + local params = getselectresponse(sql) + -- There are a few params not to put in the voicemail_values table + if not checktable("voicemail_values") then runscript(voicemail_values_creation_script) end + local ignoreparam = { username=true, ["vm-password"]=true, ["vm-password-confirm"]=true } + con:execute("START TRANSACTION") + for i,parm in ipairs(params) do + if parm.name and not ignoreparam[parm.name] then + sql = "DELETE FROM voicemail_values"..generatewhereclause(userparams.username.value).." and name='"..parm.name.."'" + assert( con:execute(sql) ) + if userparams[parm.name] and (userparams[parm.name].value ~= nil) and tostring(userparams[parm.name].value) ~= parm.value then + sql = "INSERT INTO voicemail_values VALUES('"..userparams.username.value.."', '"..parm.name.."', '"..tostring(userparams[parm.name].value).."')" + assert( con:execute(sql) ) + end + end + end + -- Set password to voicemail_prefs + if userparams["vm-password"] and userparams["vm-password"].value and userparams["vm-password"].value ~= "" then + if not checktable("voicemail_prefs") then runscript(voicemail_prefs_creation_script) end + sql = "SELECT password FROM voicemail_prefs"..generatewhereclause(userparams.username.value) + local password = getselectresponse(sql) + if #password > 0 then + -- update + sql = "UPDATE voicemail_prefs SET password='"..userparams["vm-password"].value.."'"..generatewhereclause(userparams.username.value) + else + -- insert + sql = "INSERT INTO voicemail_prefs (username, domain, password) VALUES ('"..userparams.username.value.."', '"..config.domain.."', '"..userparams["vm-password"].value.."')" + end + assert( con:execute(sql) ) + end + con:execute("COMMIT") + return success +end + +local function validateconfig(newconfig) + local success = true + if newconfig.value.domain.value == "" then + newconfig.value.domain.errtxt = "Cannot be blank" + success = false + end + if newconfig.value.database.value == "" then + newconfig.value.database.errtxt = "Cannot be blank" + success = false + end + if newconfig.value.event_socket_ip.value == "" then + newconfig.value.event_socket_ip.errtxt = "Cannot be blank" + success = false + end + if newconfig.value.event_socket_port.value == "" then + newconfig.value.event_socket_port.errtxt = "Cannot be blank" + success = false + end + if newconfig.value.event_socket_password.value == "" then + newconfig.value.event_socket_password.errtxt = "Cannot be blank" + success = false + end + return success, newconfig +end + +-- ################################################################################ +-- PUBLIC FUNCTIONS + +get_config = function() + local result = {} + result.domain = cfe({ value=config.domain, label="Domain" }) + result.database = cfe({ value=config.database, label="Database" }) + result.event_socket_ip = cfe({ value=config.event_socket_ip, label="FS Event Socket IP" }) + result.event_socket_port = cfe({ value=config.event_socket_port, label="FS Event Socket Port" }) + result.event_socket_password = cfe({ value=config.event_socket_password, label="FS Event Socket Password" }) + return cfe({ type="group", value=result, label="Voicemail Config" }) +end + +update_config = function(newconfig) + local success = validateconfig(newconfig) + if success then + for name,val in pairs(newconfig.value) do + configcontent = format.update_ini_file(configcontent, "", name, tostring(val.value)) + end + + fs.write_file(configfile, configcontent) + config = format.parse_ini_file(configcontent, "") or {} + else + newconfig.errtxt = "Failed to update config" + end + + return newconfig +end + +list_messages = function(username) + local retval = {} + local errtxt + local res, err = pcall(function() + local connected = databaseconnect() + local sql = "SELECT * FROM voicemail_msgs" + sql = sql .. generatewhereclause(username) + sql = sql .. " ORDER BY username ASC, created_epoch ASC" + retval = getselectresponse(sql) + if connected then databasedisconnect() end + end) + if not res and err then + errtxt = err + end + return cfe({ type="structure", value=retval, label="List of Messages", errtxt=errtxt }) +end + +get_message = function(message, username) + local retval = cfe({ type="raw", label="error", option="audio/x-wav" }) + local res, err = pcall(function() + local connected = databaseconnect() + local sql = "SELECT file_path FROM voicemail_msgs" + sql = sql .. generatewhereclause(username, message) + local tmp = getselectresponse(sql) + if connected then databasedisconnect() end + if #tmp == 0 then + retval.errtxt = "Invalid message" + else + retval.label = posix.basename(tmp[1].file_path) + retval.value = fs.read_file(tmp[1].file_path) + retval.length = #retval.value + end + end) + if not res and err then + retval.errtxt = err + end + return retval +end + +delete_message = function(message, username) + local retval = cfe({ label="Delete message result", errtxt="Failed to delete message - message not found" }) + local messages = format.string_to_table(message, "%s*,%s*") + local res, err = pcall(function() + local connected = databaseconnect() + local sql = "SELECT * FROM voicemail_msgs" + sql = sql .. generatewhereclause(username, messages) + local tmp = getselectresponse(sql) + if #tmp == #messages then + sql = "DELETE FROM voicemail_msgs" .. generatewhereclause(username, messages) + assert (con:execute(sql)) + for i,t in ipairs(tmp) do + os.remove(t.file_path) + end + if #messages == 1 then + retval.value = "Deleted message" + else + retval.value = "Deleted "..#messages.." messages" + end + retval.errtxt = nil + end + if connected then databasedisconnect() end + end) + if not res and err then + retval.errtxt = err + end + + return retval +end + +forward_message = function(message, newuser, username) + local retval = cfe({ label="Forward message result" }) + local messages = format.string_to_table(message, "%s*,%s*") + local res, err = pcall(function() + local connected = databaseconnect() + -- Check if message exists + local sql = "SELECT * FROM voicemail_msgs" .. generatewhereclause(username, messages) + local mess = getselectresponse(sql) + if #mess == #messages then + -- Check if newuser exists + if validuser(newuser) then + for i,m in ipairs(mess) do + -- Forward message using mod_voicemail API + -- doesn't seem like there's any way to tell whether or not it worked + voicemail_inject(newuser, config.domain, m.file_path, m.cid_number, m.cid_name) + end + if #mess == 1 then + retval.value = "Forwarded message" + else + retval.value = "Forwarded "..#mess.." messages" + end + else + retval.errtxt = "Failed to forward message - invalid user" + end + else + retval.errtxt = "Failed to forward message - message not found" + end + if connected then databasedisconnect() end + end) + if not res and err then + retval.errtxt = err + end + + return retval +end + +email_message = function(message, address, username) + local retval = cfe({ label="E-mail message result" }) + local messages = format.string_to_table(message, "%s*,%s*") + local res, err = pcall(function() + local connected = databaseconnect() + -- Check if message exists + local sql = "SELECT * FROM voicemail_msgs" .. generatewhereclause(username, messages) + local mess = getselectresponse(sql) + if #mess == #messages then + -- Create a temporary user and settings + local newuser = "tempuser"..session.random_hash(128) + while validuser(newuser) do + newuser = "tempuser"..session.random_hash(128) + end + local settings = get_usersettings(newuser) + if settings.value["vm-mailto"] and settings.value["vm-email-all-messages"] and settings.value["vm-attach-file"] and settings.value["vm-keep-local-after-email"] then + settings.value["vm-mailto"].value = address + settings.value["vm-email-all-messages"].value = true + settings.value["vm-attach-file"].value = true + settings.value["vm-keep-local-after-email"].value = false + if settings.value["vm-password"] then settings.value["vm-password"].value = "1234" end + if settings.value["vm-password-confirm"] then settings.value["vm-password-confirm"].value = "1234" end + settings = create_usersettings(settings) + if not settings.errtxt then + for i,m in ipairs(mess) do + -- E-mail message using mod_voicemail API + -- doesn't seem like there's any way to tell whether or not it worked + voicemail_inject(newuser, config.domain, m.file_path, m.cid_number, m.cid_name) + end + if #mess == 1 then + retval.value = "E-mailed message" + else + retval.value = "E-mailed "..#mess.." messages" + end + -- Now, delete the temporary user + delete_user(newuser) + else + retval.errtxt = "Failed to e-mail message - "..settings.errtxt + end + else + retval.errtxt = "Failed to e-mail message - unsupported" + end + else + retval.errtxt = "Failed to e-mail message - message not found" + end + if connected then databasedisconnect() end + end) + if not res and err then + retval.errtxt = err + end + + return retval +end + +list_users = function() + local errtxt + local users = {} + local res, err = pcall(function() + local connected = databaseconnect() + users = listusers() + -- Go in reverse order to remove the temporary users used for e-mailing messages + for i=#users,1,-1 do + u = users[i] + -- Remove the temporary users + if string.find(u.username, "^tempuser") then + table.remove(users, i) + else + local sql = "SELECT value FROM voicemail_values"..generatewhereclause(u.username).." and name='fullname'" + local cur = con:execute(sql) + if cur then + local row = cur:fetch ({}, "a") + if row and row.value then + u.fullname = row.value + end + cur:close() + end + end + end + if connected then databasedisconnect() end + end) + if not res and err then + errtxt = err + end + + return cfe({ type="structure", value=users, label="Voicemail Users", errtxt=errtxt }) +end + +delete_user = function(username) + local result = "" + local errtxt + errtxt = nil + local res, err = pcall(function() + local connected = databaseconnect() + local users = listusers(username) + if #users == 0 then + errtxt = "User does not exist" + else + -- Delete all of the user's voicemails + local messages = list_messages(username) + if #messages.value then + for i,m in ipairs(messages.value) do + delete_message(m.uuid) + end + end + -- Remove the user parameters + sql = "DELETE FROM voicemail_values " .. generatewhereclause(username) + assert (con:execute(sql)) + -- Remove the user + sql = "DELETE FROM voicemail_users " .. generatewhereclause(username) + assert (con:execute(sql)) + result = "Voicemail User Deleted" + end + if connected then databasedisconnect() end + end) + if not res and err then + errtxt = err + end + + return cfe({ value=result, errtxt=errtxt, label="Delete User Result" }) +end + +get_usersettings = function(username) + local retval = {} + local errtxt + local res, err = pcall(function() + local connected = databaseconnect() + retval = getuserparams(username) + if connected then databasedisconnect() end + end) + if not res and err then + errtxt = err + end + if retval["vm-password"] and retval["vm-password-confirm"] then retval["vm-password-confirm"].value = retval["vm-password"].value end + + return cfe({ type="group", value=retval, label="Voicemail User Settings", errtxt=errtxt }) +end + +create_usersettings = function(usersettings) + return update_usersettings(usersettings, true) +end + +update_usersettings = function(usersettings, create) + local success = true + local errtxt + -- Validate the settings + if not validator.is_integer(usersettings.value["vm-password"].value) then + success = false + usersettings.value["vm-password"].errtxt = "Password must be all numbers" + end + if usersettings.value["vm-password"].value ~= usersettings.value["vm-password-confirm"].value then + success = false + usersettings.value["vm-password-confirm"].errtxt = "Password does not match" + end + if success then + local res, err = pcall(function() + local connected = databaseconnect() + local u = listusers(usersettings.value.username.value) + if create and #u > 0 then + success = false + errtxt = "User already exists" + elseif not create and #u == 0 then + success = false + errtxt = "User does not exist" + else + if create then + sql = "INSERT INTO voicemail_users VALUES('"..escape(usersettings.value.username.value).."')" + assert (con:execute(sql)) + end + success,errtxt = setuserparams(usersettings.value) + end + if connected then databasedisconnect() end + end) + if not res and err then + success = false + errtxt = err + end + end + if not success then + if create then + usersettings.errtxt = errtxt or "Failed to create user" + else + usersettings.errtxt = errtxt or "Failed to save settings" + end + end + return usersettings +end + +process_directory_xml_request = function(input) + local output = {} + local errtxt + local res, err = pcall(function() + local connected = databaseconnect() + if validuser(input.user) then + output = getuserparams(input.user) + -- Add the domain + output.domain = cfe({ value=input.domain }) + else + errtxt = "User not found" + end + if connected then databasedisconnect() end + end) + if not res and err then + errtxt = err + end + return cfe({ type="group", value=output, label="Directory Data", errtxt=errtxt }) +end + +process_dialplan_xml_request = function(input) + local output = {} + local errtxt + local res, err = pcall(function() + local connected = databaseconnect() + if validuser(input["Caller-Destination-Number"]) then + output.domain = cfe({ value=config.domain }) + output.username = cfe({ value=input["Caller-Destination-Number"] }) + else + errtxt = "User not found" + end + if connected then databasedisconnect() end + end) + if not res and err then + errtxt = err + end + return cfe({ type="group", value=output, label="Dialplan Data", errtxt=errtxt }) +end diff --git a/vmail.menu b/vmail.menu new file mode 100644 index 0000000..e6c8895 --- /dev/null +++ b/vmail.menu @@ -0,0 +1,7 @@ +# Prefix and controller are already known at this point +# Cat Group Tab Action +Applications 88Voicemail Users editusers +Applications 88Voicemail Voicemail listmessages +Applications 88Voicemail Voicemail listmymessages +Applications 88Voicemail Settings editmyusersettings +Applications 88Voicemail Config editconfig diff --git a/vmail.roles b/vmail.roles new file mode 100644 index 0000000..6d72cb8 --- /dev/null +++ b/vmail.roles @@ -0,0 +1,5 @@ +GUEST=vmail:processdialplanxml,vmail:processdirectoryxml +USER=vmail:listusers,vmail:listmymessages,vmail:downloadmymessage,vmail:deletemymessage,vmail:editmyusersettings,vmail:forwardmymessage,vmail:emailmymessage +EDITOR=vmail:editusers,vmail:createuser,vmail:deleteuser,vmail:editusersettings +EXPERT=vmail:editconfig,vmail:listmessages,vmail:downloadmessage,vmail:deletemessage,vmail:forwardmessage,vmail:emailmessage +ADMIN=vmail:listmessages,vmail:downloadmessage,vmail:deletemessage,vmail:forwardmessage,vmail:emailmessage,vmail:editusersettings,vmail:listusers,vmail:editusers,vmail:createuser,vmail:deleteuser,vmail:editconfig -- cgit v1.2.3