diff options
Diffstat (limited to 'kamailio-model.lua')
-rw-r--r-- | kamailio-model.lua | 588 |
1 files changed, 588 insertions, 0 deletions
diff --git a/kamailio-model.lua b/kamailio-model.lua new file mode 100644 index 0000000..ed05f85 --- /dev/null +++ b/kamailio-model.lua @@ -0,0 +1,588 @@ +module(..., package.seeall) + +-- Load libraries +require("posix") +require("modelfunctions") +require("fs") +require("format") +require("validator") + +-- Set variables +local processname = "kamailio" +local packagename = "kamailio" +local baseurl = "/etc/kamailio" +local kamctlrc_file = "/etc/kamailio/kamctlrc" + +local path = "PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin " +local env +local con +local DBENGINE + +-- ################################################################################ +-- DATABASE FUNCTIONS + +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 "" + return string.gsub(sql, "'", "''") +end + +local databaseconnect = function() + if not con then + -- parse the kamctlrc file + local config = format.parse_ini_file(fs.read_file(kamctlrc_file), "") or {} + if not config.DBENGINE then + error("Database engine not specified, please setup one in the config script "..kamctlrc_file) + end + + -- create environment object + if config.DBENGINE == "MYSQL" or config.DBENGINE == "mysql" or config.DBENGINE == "MySQL" then + error("MYSQL database not supported") + elseif config.DBENGINE == "PGSQL" or config.DBENGINE == "pgsql" or config.DBENGINE == "postgres" or config.DBENGINE == "postgresql" or config.DBENGINE == "POSTGRESQL" then + require("luasql.postgres") + env = assert (luasql.postgres()) + DBENGINE = "PGSQL" + elseif config.DBENGINE == "ORACLE" or config.DBENGINE == "oracle" or config.DBENGINE == "Oracle" then + error("ORACLE database not supported") + elseif config.DBENGINE == "DBTEXT" or config.DBENGINE == "dbtext" or config.DBENGINE == "textdb" then + error("DBTEXT database not supported") + elseif config.DBENGINE == "DB_BERKELEY" or config.DBENGINE == "db_berkeley" or config.DBENGINE == "BERKELEY" or config.DBENGINE == "berkeley" then + error("BERKELEY database not supported") + else + error("Unknown database engine "..config.DBENGINE) + end + + -- connect to data source + con = assert(env:connect(config.DBNAME or "", config.DBRWUSER or "", config.DBRWPW or "", config.DBHOST, config.DBPORT)) + 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 runsqlcommand = function(sql) +logevent(sql) + assert(con:execute(sql)) +end + +local getselectresponse = function(sql) + local retval = {} +logevent(sql) + 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 listtables = function() + local result = {} + if DBENGINE == "PGSQL" then + local tab = getselectresponse("SELECT tablename, schemaname FROM pg_tables WHERE tablename !~* 'pg_*' ORDER BY schemaname, tablename ASC") + for i,t in ipairs(tab) do + result[#result+1] = {schema=t.schemaname, table=t.tablename} + end + else + -- untested + result = con:tables() + end + return result +end + +local listcolumns = function(table, schema) + local result = {} + if DBENGINE == "PGSQL" then + local col + if schema then + col = getselectresponse("SELECT column_name AS field FROM information_schema.columns WHERE table_schema='"..schema.."' AND table_name='"..table.."' ORDER BY ordinal_position") + else + col = getselectresponse("SELECT column_name AS field FROM information_schema.columns WHERE table_name='"..table.."' ORDER BY ordinal_position") + end + + for i,c in ipairs(col) do + result[#result+1] = c.field + end + end + return result +end + +-- ################################################################################ +-- LOCAL FUNCTIONS + +local is_valid_filename = function(filename) + local dirname = posix.dirname(filename) + return validator.is_valid_filename(filename) and string.match(dirname, baseurl) and not string.match(dirname, "%.%.") +end + +local function validate_user(user) + local success = true + if user.value.username.value == "" then + user.value.username.errtxt = "Cannot be empty" + success = false + end + if user.value.password.value == "" then + user.value.password.errtxt = "Cannot be empty" + success = false + end + if user.value.password.value ~= user.value.password_confirm.value then + user.value.password_confirm.errtxt = "Must match password" + success = false + end + return success, user +end + +-- ################################################################################ +-- PUBLIC FUNCTIONS + +function startstop_service(action) + return modelfunctions.startstop_service(processname, action) +end + +function getstatus() + return modelfunctions.getstatus(processname, packagename, "Kamailio Status") +end + +function get_filedetails(filename) + return modelfunctions.getfiledetails(filename, is_valid_filename) +end + +function update_filedetails(filedetails) + return modelfunctions.setfiledetails(filedetails, is_valid_filename) +end + +function reloadplan() + local cmd = path .. " sercmd mi_dg dp_reload" + local f = io.popen(cmd) + local result = f:read("*a") + f:close() + return cfe({value=result, label="Reloading Dial Plan Result"}) +end + +function list_files() + local retval = {} + for file in fs.find(null, baseurl) do + local details = fs.stat(file) + if details.type == "regular" then + details.filename = file + table.insert(retval, details) + end + end + table.sort(retval, function(a,b) return a.filename < b.filename end) + return cfe({ type="structure", value=retval, label="List of Kamailio files" }) +end + +local function parse_db_show(table) + local cmd = path .. "kamctl db show "..(table or "") + local f = io.popen(cmd) + -- These settings work for Postgres and DBTEXT database + local delimiter = "\'?%s*[,|]%s*\'?" + local results = {} + local errtxt + for line in f:lines() do + if #results == 0 and string.match(line, "^ERROR:") then + errtxt = line + results = nil + break + end + if string.match(line, "^[+-]+$") then + results = {} + else + local words = format.string_to_table(line, delimiter) + if words and #words > 0 then + results[#results+1] = words + end + end + end + f:close() + return results, errtxt +end + +function list_users() + -- Database format: id | username | domain | password | email_address | ha1 | ha1b | rpid + local results = {} + local r, errtxt + r, errtxt = parse_db_show("subscriber") + for i,words in ipairs(r or {}) do + if #words > 1 then + local temp = {username = words[2], + --domain = words[3], + password = words[4], + --email_address = words[5] + } + results[#results+1] = temp + end + end + table.sort(results, function(a,b) return a.username < b.username end) + return cfe({type="list", value=results, label="Kamailio Users"}) +end + +function get_new_user() + local user = {} + user.username = cfe({label="User Name"}) + user.password = cfe({label="Password"}) + user.password_confirm = cfe({label="Password (confirm)"}) + --user.email_address = cfe({label="E-mail Address"}) + return cfe({type="group", value=user, label="Kamailio User"}) +end + +function create_new_user(user) + local success = validate_user(user) + if success then + local cmd = path .. "kamctl add "..format.escapespecialcharacters(user.value.username.value).." "..format.escapespecialcharacters(user.value.password.value) + --if user.value.email_address.value ~= "" then + -- cmd = cmd.." "..format.escapespecialcharacters(user.value.email_address.value) + --end + local f = io.popen(cmd) + user.descr = f:read("*a") + f:close() + else + user.errtxt = "Failed to create new user" + end + + return user +end + +function delete_user(username) + local cmd = path .. "kamctl rm "..format.escapespecialcharacters(username) + local f = io.popen(cmd) + local result = f:read("*a") + f:close() + return cfe({value=result, label="Delete User Result"}) +end + +function get_user(username) + local user = get_new_user() + user.value.username.value = username + user.value.username.errtxt = "Invalid user" + local users = list_users() + for i,u in ipairs(users.value) do + if u.username == username then + user.value.username.errtxt = nil + break + end + end + return user +end + +function update_user(user) + local success = validate_user(user) + if success then + local cmd = path .. "kamctl passwd "..format.escapespecialcharacters(user.value.username.value).." "..format.escapespecialcharacters(user.value.password.value) + local f = io.popen(cmd) + user.descr = f:read("*a") + f:close() + else + user.errtxt = "Failed to update user" + end + + return user +end + +function list_tables() + local retval = {} + local errtxt + -- Get the devices from the DB + local res, err = pcall(function() + local connected = databaseconnect() + retval = listtables() + if connected then databasedisconnect() end + end) + if not res and err then + errtxt = err + end + + return cfe({ type="list", value=retval, label="List of Database Tables", errtxt=errtxt }) +end + +function list_table_entries(req_table) + local retval = {} + -- split up the schema from the table + local schema, table + schema, table = string.match(req_table, "(.+)%.(.+)") + -- if there's no schema with it then grab the req_table value + if not schema then + table = req_table + end + retval.table = cfe({ value=req_table or "", label="Table" }) + retval.fields = cfe({ type="list", value={}, label="List of Table Fields" }) + retval.entries = cfe({ type="structure", value={}, label="List of Database Entries" }) + local errtxt + -- Get the devices from the DB + local res, err = pcall(function() + local connected = databaseconnect() + local tables = listtables() + retval.table.errtxt = "Table does not exist" + errtxt = "Table does not exist" + for i,t in ipairs(tables) do + if t.table == table then + retval.table.errtxt = nil + errtxt = nil + -- if there's a schema, include it in the select statement + if schema then + retval.entries.value = getselectresponse("SELECT * FROM "..schema.."."..table) or {} + else + retval.entries.value = getselectresponse("SELECT * FROM "..table) or {} + end + retval.fields.value = listcolumns(table, schema) or {} + end + end + if connected then databasedisconnect() end + end) + if not res and err then + errtxt = err + end + return cfe({ type="group", value=retval, label="Database Table Entries", errtxt=errtxt }) +end + +function get_table_entry(req_table, id) + local retval = {} + -- split up the schema from the table + local schema, table + schema, table = string.match(req_table, "(.+)%.(.+)") + -- if there's no schema with it then grab the req_table value + if not schema then + table = req_table + end + retval.table = cfe({ value=req_table or "", label="Table", errtxt="Table does not exist", seq=0 }) + local errtxt = "Table does not exist" + if req_table and req_table ~= "" then + local res, err = pcall(function() + local connected = databaseconnect() + local tables = listtables() + for i,t in ipairs(tables) do + if t.table == table then + retval.table.errtxt = nil + errtxt = nil + break + end + end + if not errtxt then + local fields = listcolumns(table, schema) + for i,f in ipairs(fields) do + retval[f] = cfe({ label=f, seq=i }) + end + if id and id ~= "" then + local entry + if schema then + entry = getselectresponse("SELECT * FROM "..schema.."."..table.." WHERE id='"..escape(id).."'") + else + entry = getselectresponse("SELECT * FROM "..table.." WHERE id='"..escape(id).."'") + end + if entry and #entry > 0 then + for n,v in pairs(entry[1]) do + if retval[n] then retval[n].value = v end + end + end + end + end + if connected then databasedisconnect() end + end) + if not res and err then + errtxt = err + end + end + + return cfe({ type="group", value=retval, label="Database Table Entry", errtxt=errtxt }) +end + +function create_table_entry(entry) + return update_table_entry(entry, true) +end + +function update_table_entry(entry, create) + local success = true + local errtxt + -- Validate the settings + -- relying on get_table_entry to do the validation of table + if entry.value.table.value == "" or entry.value.table.errtxt then + success = false + entry.value.table.errtxt = "Table does not exist" + end + if not create then + if not entry.value.id then + success = false + elseif not entry.value.id.value or entry.value.id.value == "" then + success = false + entry.value.id.errtxt = "Invalid id" + end + end + if success then + local res, err = pcall(function() + local connected = databaseconnect() + local tables = listtables() + local schema, tablename + schema, tablename = string.match(entry.value.table.value, "(.+)%.(.+)") + success = false + if not schema then + tablename = entry.value.table.value + end + entry.value.table.errtxt = "Table does not exist" + for i,t in ipairs(tables) do + if t.table == tablename then + success = true + entry.value.table.errtxt = nil + break + end + end + if success and not create then + local sql = "SELECT * FROM "..entry.value.table.value.." WHERE id='"..escape(entry.value.id.value).."'" + local tmp = getselectresponse(sql) + if not tmp or #tmp == 0 then + success = false + errtxt = "Entry does not exist" + end + end + if success then + local names = {} + local values = {} + for n,v in pairs(entry.value) do + if n ~= "table" and n ~= "id" then + --- !!! HACK !!! --- + --- Fix for DISTributary Phone System Tool: ASHP + --- + --- Need to allow for insertion of NULL rather than + --- simply '' so that integers and booleans can be + --- added with empty values. + ---- ----------- --- + if n == "role_id" or n == "seq" then + if v.value == "" then + v.value = "0" + end + end + names[#names+1] = n + values[#values+1] = escape(v.value) + end + end + if create then + sql = "INSERT INTO "..entry.value.table.value.." ("..table.concat(names, ", ")..") VALUES('"..table.concat(values, "', '").."')" + else + sql = "UPDATE "..entry.value.table.value.." SET ("..table.concat(names, ", ")..") = ('"..table.concat(values, "', '").."') WHERE id='"..escape(entry.value.id.value).."'" + end + runsqlcommand(sql) + 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 + entry.errtxt = errtxt or "Failed to create entry" + else + entry.errtxt = errtxt or "Failed to save entry" + end + end + return entry +end + +function delete_table_entry(req_table, id) + local result = "" + local errtxt + -- split up the schema from the table + local schema, table + schema, table = string.match(req_table, "(.+)%.(.+)") + -- if there's no schema with it then grab the req_table value + if not schema then + table = req_table + end + if not req_table or req_table == "" then + errtxt = "Invalid table" + elseif not id or id == "" then + errtxt = "Invalid entry" + else + local res, err = pcall(function() + local connected = databaseconnect() + errtxt = "Invalid table" + local tables = listtables() + for i,t in ipairs(tables) do + if t.table == table then + errtxt = nil + break + end + end + if not errtxt then + local sql + if schema then + sql = "DELETE FROM "..schema.."."..table.." WHERE id='"..escape(id).."'" + else + sql = "DELETE FROM "..table.." WHERE id='"..escape(id).."'" + end + runsqlcommand(sql) + result = "Entry Deleted" + end + if connected then databasedisconnect() end + end) + if not res and err then + errtxt = err + end + end + + return cfe({ value=result, errtxt=errtxt, label="Delete Entry Result" }) +end + +function create_database() + local cmd = path.."echo -e 'y\ny\n' | "..path.."kamdbctl create 2>&1" + local f = io.popen(cmd) + local result = f:read("*a") + return cfe({ value=result, label="Create database result" }) +end + +function search_database(id, value, comparison) + local errtxt + retval = {} + retval.id = cfe({type="select", value=id or "", label="Table.Column", option={}, seq=1}) + retval.comparison = cfe({type="select", value=comparison or "=", label="Comparison", option={"=", "!=", "~", "!~", "~*", "!*~"}, seq=2}) + retval.value = cfe({label="Value", value=value or "", descr="Value or SQL regular expression", seq=3}) + local res, err = pcall(function() + local connected = databaseconnect() + local tables = listtables() or {} + for i,t in ipairs(tables) do + local columns = listcolumns(t.table) or {} + for i,c in ipairs(columns) do + retval.id.option[#retval.id.option + 1] = t.table.."."..c + end + end + -- Get the rows from the DB + if id and modelfunctions.validateselect(retval.id) and modelfunctions.validateselect(retval.comparison) then + retval.result = cfe({type="structure", value={}, label="List of Rows", seq=4 }) + local table, column = string.match(id, "^([^.]*)%.(.*)") + if table then + local sql = "SELECT * FROM "..table.." WHERE "..column..comparison.."'"..value.."'" + retval.result.value = getselectresponse(sql) + end + end + if connected then databasedisconnect() end + end) + if not res and err then + errtxt = err + end + return cfe({type="group", value=retval, label="Database Search", errtxt=errtxt}) +end + |