diff options
Diffstat (limited to 'applications/luci-app-pbx/luasrc/model')
6 files changed, 1203 insertions, 0 deletions
diff --git a/applications/luci-app-pbx/luasrc/model/cbi/pbx-advanced.lua b/applications/luci-app-pbx/luasrc/model/cbi/pbx-advanced.lua new file mode 100644 index 0000000000..5d4f135c5b --- /dev/null +++ b/applications/luci-app-pbx/luasrc/model/cbi/pbx-advanced.lua @@ -0,0 +1,293 @@ +--[[ + Copyright 2011 Iordan Iordanov <iiordanov (AT) gmail.com> + + This file is part of luci-pbx. + + luci-pbx is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + luci-pbx is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with luci-pbx. If not, see <http://www.gnu.org/licenses/>. +]]-- + +if nixio.fs.access("/etc/init.d/asterisk") then + server = "asterisk" +elseif nixio.fs.access("/etc/init.d/freeswitch") then + server = "freeswitch" +else + server = "" +end + +appname = "PBX" +modulename = "pbx-advanced" +defaultbindport = 5060 +defaultrtpstart = 19850 +defaultrtpend = 19900 + +-- Returns all the network related settings, including a constructed RTP range +function get_network_info() + externhost = m.uci:get(modulename, "advanced", "externhost") + ipaddr = m.uci:get("network", "lan", "ipaddr") + bindport = m.uci:get(modulename, "advanced", "bindport") + rtpstart = m.uci:get(modulename, "advanced", "rtpstart") + rtpend = m.uci:get(modulename, "advanced", "rtpend") + + if bindport == nil then bindport = defaultbindport end + if rtpstart == nil then rtpstart = defaultrtpstart end + if rtpend == nil then rtpend = defaultrtpend end + + if rtpstart == nil or rtpend == nil then + rtprange = nil + else + rtprange = rtpstart .. "-" .. rtpend + end + + return bindport, rtprange, ipaddr, externhost +end + +-- If not present, insert empty rules in the given config & section named PBX-SIP and PBX-RTP +function insert_empty_sip_rtp_rules(config, section) + + -- Add rules named PBX-SIP and PBX-RTP if not existing + found_sip_rule = false + found_rtp_rule = false + m.uci:foreach(config, section, + function(s1) + if s1._name == 'PBX-SIP' then + found_sip_rule = true + elseif s1._name == 'PBX-RTP' then + found_rtp_rule = true + end + end) + + if found_sip_rule ~= true then + newrule=m.uci:add(config, section) + m.uci:set(config, newrule, '_name', 'PBX-SIP') + end + if found_rtp_rule ~= true then + newrule=m.uci:add(config, section) + m.uci:set(config, newrule, '_name', 'PBX-RTP') + end +end + +-- Delete rules in the given config & section named PBX-SIP and PBX-RTP +function delete_sip_rtp_rules(config, section) + + -- Remove rules named PBX-SIP and PBX-RTP + commit = false + m.uci:foreach(config, section, + function(s1) + if s1._name == 'PBX-SIP' or s1._name == 'PBX-RTP' then + m.uci:delete(config, s1['.name']) + commit = true + end + end) + + -- If something changed, then we commit the config. + if commit == true then m.uci:commit(config) end +end + +-- Deletes QoS rules associated with this PBX. +function delete_qos_rules() + delete_sip_rtp_rules ("qos", "classify") +end + + +function insert_qos_rules() + -- Insert empty PBX-SIP and PBX-RTP rules if not present. + insert_empty_sip_rtp_rules ("qos", "classify") + + -- Get the network information + bindport, rtprange, ipaddr, externhost = get_network_info() + + -- Iterate through the QoS rules, and if there is no other rule with the same port + -- range at the priority service level, insert this rule. + commit = false + m.uci:foreach("qos", "classify", + function(s1) + if s1._name == 'PBX-SIP' then + if s1.ports ~= bindport or s1.target ~= "Priority" or s1.proto ~= "udp" then + m.uci:set("qos", s1['.name'], "ports", bindport) + m.uci:set("qos", s1['.name'], "proto", "udp") + m.uci:set("qos", s1['.name'], "target", "Priority") + commit = true + end + elseif s1._name == 'PBX-RTP' then + if s1.ports ~= rtprange or s1.target ~= "Priority" or s1.proto ~= "udp" then + m.uci:set("qos", s1['.name'], "ports", rtprange) + m.uci:set("qos", s1['.name'], "proto", "udp") + m.uci:set("qos", s1['.name'], "target", "Priority") + commit = true + end + end + end) + + -- If something changed, then we commit the qos config. + if commit == true then m.uci:commit("qos") end +end + +-- This function is a (so far) unsuccessful attempt to manipulate the firewall rules from here +-- Need to do more testing and eventually move to this mode. +function maintain_firewall_rules() + -- Get the network information + bindport, rtprange, ipaddr, externhost = get_network_info() + + commit = false + -- Only if externhost is set, do we control firewall rules. + if externhost ~= nil and bindport ~= nil and rtprange ~= nil then + -- Insert empty PBX-SIP and PBX-RTP rules if not present. + insert_empty_sip_rtp_rules ("firewall", "rule") + + -- Iterate through the firewall rules, and if the dest_port and dest_ip setting of the\ + -- SIP and RTP rule do not match what we want configured, set all the entries in the rule\ + -- appropriately. + m.uci:foreach("firewall", "rule", + function(s1) + if s1._name == 'PBX-SIP' then + if s1.dest_port ~= bindport then + m.uci:set("firewall", s1['.name'], "dest_port", bindport) + m.uci:set("firewall", s1['.name'], "src", "wan") + m.uci:set("firewall", s1['.name'], "proto", "udp") + m.uci:set("firewall", s1['.name'], "target", "ACCEPT") + commit = true + end + elseif s1._name == 'PBX-RTP' then + if s1.dest_port ~= rtprange then + m.uci:set("firewall", s1['.name'], "dest_port", rtprange) + m.uci:set("firewall", s1['.name'], "src", "wan") + m.uci:set("firewall", s1['.name'], "proto", "udp") + m.uci:set("firewall", s1['.name'], "target", "ACCEPT") + commit = true + end + end + end) + else + -- We delete the firewall rules if one or more of the necessary parameters are not set. + sip_rule_name=nil + rtp_rule_name=nil + + -- First discover the configuration names of the rules. + m.uci:foreach("firewall", "rule", + function(s1) + if s1._name == 'PBX-SIP' then + sip_rule_name = s1['.name'] + elseif s1._name == 'PBX-RTP' then + rtp_rule_name = s1['.name'] + end + end) + + -- Then, using the names, actually delete the rules. + if sip_rule_name ~= nil then + m.uci:delete("firewall", sip_rule_name) + commit = true + end + if rtp_rule_name ~= nil then + m.uci:delete("firewall", rtp_rule_name) + commit = true + end + end + + -- If something changed, then we commit the firewall config. + if commit == true then m.uci:commit("firewall") end +end + +m = Map (modulename, translate("Advanced Settings"), + translate("This section contains settings that do not need to be changed under \ + normal circumstances. In addition, here you can configure your system \ + for use with remote SIP devices, and resolve call quality issues by enabling \ + the insertion of QoS rules.")) + +-- Recreate the voip server config, and restart necessary services after changes are commited +-- to the advanced configuration. The firewall must restart because of "Remote Usage". +function m.on_after_commit(self) + + -- Make sure firewall rules are in place + maintain_firewall_rules() + + -- If insertion of QoS rules is enabled + if m.uci:get(modulename, "advanced", "qos_enabled") == "yes" then + insert_qos_rules() + else + delete_qos_rules() + end + + luci.sys.call("/etc/init.d/pbx-" .. server .. " restart 1\>/dev/null 2\>/dev/null") + luci.sys.call("/etc/init.d/" .. server .. " restart 1\>/dev/null 2\>/dev/null") + luci.sys.call("/etc/init.d/firewall restart 1\>/dev/null 2\>/dev/null") +end + +----------------------------------------------------------------------------- +s = m:section(NamedSection, "advanced", "settings", translate("Advanced Settings")) +s.anonymous = true + +s:tab("general", translate("General Settings")) +s:tab("remote_usage", translate("Remote Usage"), + translatef("You can use your SIP devices/softphones with this system from a remote location \ + as well, as long as your Internet Service Provider gives you a public IP. \ + You will be able to call other local users for free (e.g. other Analog Telephone Adapters (ATAs)) \ + and use your VoIP providers to make calls as if you were local to the PBX. \ + After configuring this tab, go back to where users are configured and see the new \ + Server and Port setting you need to configure the remote SIP devices with. Please note that if this \ + PBX is not running on your router/gateway, you will need to configure port forwarding (NAT) on your \ + router/gateway. Please forward the ports below (SIP port and RTP range) to the IP address of the \ + device running this PBX.")) + +s:tab("qos", translate("QoS Settings"), + translate("If you experience jittery or high latency audio during heavy downloads, you may want \ + to enable QoS. QoS prioritizes traffic to and from your network for specified ports and IP \ + addresses, resulting in better latency and throughput for sound in our case. If enabled below, \ + a QoS rule for this service will be configured by the PBX automatically, but you must visit the \ + QoS configuration page (Network->QoS) to configure other critical QoS settings like Download \ + and Upload speed.")) + +ringtime = s:taboption("general", Value, "ringtime", translate("Number of Seconds to Ring"), + translate("Set the number of seconds to ring users upon incoming calls before hanging up \ + or going to voicemail, if the voicemail is installed and enabled.")) +ringtime.datatype = "port" +ringtime.default = 30 + +ua = s:taboption("general", Value, "useragent", translate("User Agent String"), + translate("This is the name that the VoIP server will use to identify itself when \ + registering to VoIP (SIP) providers. Some providers require this to a specific \ + string matching a hardware SIP device.")) +ua.default = appname + +h = s:taboption("remote_usage", Value, "externhost", translate("Domain/IP Address/Dynamic Domain"), + translate("You can enter your domain name, external IP address, or dynamic domain name here. \ + The best thing to input is a static IP address. If your IP address is dynamic and it changes, \ + your configuration will become invalid. Hence, it's recommended to set up Dynamic DNS in this case. \ + and enter your Dynamic DNS hostname here. You can configure Dynamic DNS with the luci-app-ddns package.")) +h.datatype = "host" + +p = s:taboption("remote_usage", Value, "bindport", translate("External SIP Port"), + translate("Pick a random port number between 6500 and 9500 for the service to listen on. \ + Do not pick the standard 5060, because it is often subject to brute-force attacks. \ + When finished, (1) click \"Save and Apply\", and (2) look in the \ + \"SIP Device/Softphone Accounts\" section for updated Server and Port settings \ + for your SIP Devices/Softphones.")) +p.datatype = "port" + +p = s:taboption("remote_usage", Value, "rtpstart", translate("RTP Port Range Start"), + translate("RTP traffic carries actual voice packets. This is the start of the port range \ + that will be used for setting up RTP communication. It's usually OK to leave this \ + at the default value.")) +p.datatype = "port" +p.default = defaultrtpstart + +p = s:taboption("remote_usage", Value, "rtpend", translate("RTP Port Range End")) +p.datatype = "port" +p.default = defaultrtpend + +p = s:taboption("qos", ListValue, "qos_enabled", translate("Insert QoS Rules")) +p:value("yes", translate("Yes")) +p:value("no", translate("No")) +p.default = "yes" + +return m diff --git a/applications/luci-app-pbx/luasrc/model/cbi/pbx-calls.lua b/applications/luci-app-pbx/luasrc/model/cbi/pbx-calls.lua new file mode 100644 index 0000000000..ca373d63a3 --- /dev/null +++ b/applications/luci-app-pbx/luasrc/model/cbi/pbx-calls.lua @@ -0,0 +1,424 @@ +--[[ + Copyright 2011 Iordan Iordanov <iiordanov (AT) gmail.com> + + This file is part of luci-pbx. + + luci-pbx is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + luci-pbx is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with luci-pbx. If not, see <http://www.gnu.org/licenses/>. +]]-- + +if nixio.fs.access("/etc/init.d/asterisk") then + server = "asterisk" +elseif nixio.fs.access("/etc/init.d/freeswitch") then + server = "freeswitch" +else + server = "" +end + +modulename = "pbx-calls" +voipmodulename = "pbx-voip" +googlemodulename = "pbx-google" +usersmodulename = "pbx-users" +allvalidaccounts = {} +nallvalidaccounts = 0 +validoutaccounts = {} +nvalidoutaccounts = 0 +validinaccounts = {} +nvalidinaccounts = 0 +allvalidusers = {} +nallvalidusers = 0 +validoutusers = {} +nvalidoutusers = 0 + + +-- Checks whether the entered extension is valid syntactically. +function is_valid_extension(exten) + return (exten:match("[#*+0-9NXZ]+$") ~= nil) +end + + +m = Map (modulename, translate("Call Routing"), + translate("This is where you indicate which Google/SIP accounts are used to call what \ + country/area codes, which users can use what SIP/Google accounts, how incoming \ + calls are routed, what numbers can get into this PBX with a password, and what \ + numbers are blacklisted.")) + +-- Recreate the config, and restart services after changes are commited to the configuration. +function m.on_after_commit(self) + luci.sys.call("/etc/init.d/pbx-" .. server .. " restart 1\>/dev/null 2\>/dev/null") + luci.sys.call("/etc/init.d/" .. server .. " restart 1\>/dev/null 2\>/dev/null") +end + +-- Add Google accounts to all valid accounts, and accounts valid for incoming and outgoing calls. +m.uci:foreach(googlemodulename, "gtalk_jabber", + function(s1) + -- Add this provider to list of valid accounts. + if s1.username ~= nil and s1.name ~= nil then + allvalidaccounts[s1.name] = s1.username + nallvalidaccounts = nallvalidaccounts + 1 + + if s1.make_outgoing_calls == "yes" then + -- Add provider to the associative array of valid outgoing accounts. + validoutaccounts[s1.name] = s1.username + nvalidoutaccounts = nvalidoutaccounts + 1 + end + + if s1.register == "yes" then + -- Add provider to the associative array of valid outgoing accounts. + validinaccounts[s1.name] = s1.username + nvalidinaccounts = nvalidinaccounts + 1 + end + end + end) + +-- Add SIP accounts to all valid accounts, and accounts valid for incoming and outgoing calls. +m.uci:foreach(voipmodulename, "voip_provider", + function(s1) + -- Add this provider to list of valid accounts. + if s1.defaultuser ~= nil and s1.host ~= nil and s1.name ~= nil then + allvalidaccounts[s1.name] = s1.defaultuser .. "@" .. s1.host + nallvalidaccounts = nallvalidaccounts + 1 + + if s1.make_outgoing_calls == "yes" then + -- Add provider to the associative array of valid outgoing accounts. + validoutaccounts[s1.name] = s1.defaultuser .. "@" .. s1.host + nvalidoutaccounts = nvalidoutaccounts + 1 + end + + if s1.register == "yes" then + -- Add provider to the associative array of valid outgoing accounts. + validinaccounts[s1.name] = s1.defaultuser .. "@" .. s1.host + nvalidinaccounts = nvalidinaccounts + 1 + end + end + end) + +-- Add Local User accounts to all valid users, and users allowed to make outgoing calls. +m.uci:foreach(usersmodulename, "local_user", + function(s1) + -- Add user to list of all valid users. + if s1.defaultuser ~= nil then + allvalidusers[s1.defaultuser] = true + nallvalidusers = nallvalidusers + 1 + + if s1.can_call == "yes" then + validoutusers[s1.defaultuser] = true + nvalidoutusers = nvalidoutusers + 1 + end + end + end) + + +---------------------------------------------------------------------------------------------------- +-- If there are no accounts configured, or no accounts enabled for outgoing calls, display a warning. +-- Otherwise, display the usual help text within the section. +if nallvalidaccounts == 0 then + text = translate("NOTE: There are no Google or SIP provider accounts configured.") +elseif nvalidoutaccounts == 0 then + text = translate("NOTE: There are no Google or SIP provider accounts enabled for outgoing calls.") +else + text = translate("If you have more than one account that can make outgoing calls, you \ + should enter a list of phone numbers and/or prefixes in the following fields for each \ + provider listed. Invalid prefixes are removed silently, and only 0-9, X, Z, N, #, *, \ + and + are valid characters. The letter X matches 0-9, Z matches 1-9, and N matches 2-9. \ + For example to make calls to Germany through a provider, you can enter 49. To make calls \ + to North America, you can enter 1NXXNXXXXXX. If one of your providers can make \"local\" \ + calls to an area code like New York's 646, you can enter 646NXXXXXX for that \ + provider. You should leave one account with an empty list to make calls with \ + it by default, if no other provider's prefixes match. The system will automatically \ + replace an empty list with a message that the provider dials all numbers not matched by another \ + provider's prefixes. Be as specific as possible (i.e. 1NXXNXXXXXX is better than 1). Please note \ + all international dial codes are discarded (e.g. 00, 011, 010, 0011). Entries can be made in a \ + space-separated list, and/or one per line by hitting enter after every one.") +end + + +s = m:section(NamedSection, "outgoing_calls", "call_routing", translate("Outgoing Calls"), text) +s.anonymous = true + +for k,v in pairs(validoutaccounts) do + patterns = s:option(DynamicList, k, v) + + -- If the saved field is empty, we return a string + -- telling the user that this provider would dial any exten. + function patterns.cfgvalue(self, section) + value = self.map:get(section, self.option) + + if value == nil then + return {translate("Dials numbers unmatched elsewhere")} + else + return value + end + end + + -- Write only valid extensions into the config file. + function patterns.write(self, section, value) + newvalue = {} + nindex = 1 + for index, field in ipairs(value) do + val = luci.util.trim(value[index]) + if is_valid_extension(val) == true then + newvalue[nindex] = val + nindex = nindex + 1 + end + end + DynamicList.write(self, section, newvalue) + end +end + +---------------------------------------------------------------------------------------------------- +-- If there are no accounts configured, or no accounts enabled for incoming calls, display a warning. +-- Otherwise, display the usual help text within the section. +if nallvalidaccounts == 0 then + text = translate("NOTE: There are no Google or SIP provider accounts configured.") +elseif nvalidinaccounts == 0 then + text = translate("NOTE: There are no Google or SIP provider accounts enabled for incoming calls.") +else + text = translate("For each provider enabled for incoming calls, here you can restrict which users to\ + ring on incoming calls. If the list is empty, the system will indicate that all users \ + enabled for incoming calls will ring. Invalid usernames will be rejected \ + silently. Also, entering a username here overrides the user's setting to not receive \ + incoming calls. This way, you can make certain users ring only for specific providers. \ + Entries can be made in a space-separated list, and/or one per line by hitting enter after \ + every one.") +end + + +s = m:section(NamedSection, "incoming_calls", "call_routing", translate("Incoming Calls"), text) +s.anonymous = true + +for k,v in pairs(validinaccounts) do + users = s:option(DynamicList, k, v) + + -- If the saved field is empty, we return a string telling the user that + -- this provider would ring all users configured for incoming calls. + function users.cfgvalue(self, section) + value = self.map:get(section, self.option) + + if value == nil then + return {translate("Rings users enabled for incoming calls")} + else + return value + end + end + + -- Write only valid user names. + function users.write(self, section, value) + newvalue = {} + nindex = 1 + for index, field in ipairs(value) do + trimuser = luci.util.trim(value[index]) + if allvalidusers[trimuser] == true then + newvalue[nindex] = trimuser + nindex = nindex + 1 + end + end + DynamicList.write(self, section, newvalue) + end +end + + +---------------------------------------------------------------------------------------------------- +-- If there are no user accounts configured, no user accounts enabled for outgoing calls, +-- display a warning. Otherwise, display the usual help text within the section. +if nallvalidusers == 0 then + text = translate("NOTE: There are no local user accounts configured.") +elseif nvalidoutusers == 0 then + text = translate("NOTE: There are no local user accounts enabled for outgoing calls.") +else + text = translate("For each user enabled for outgoing calls you can restrict what providers the user \ + can use for outgoing calls. By default all users can use all providers. To show up in the list \ + below the user should be allowed to make outgoing calls in the \"User Accounts\" page. Enter VoIP \ + providers in the format username@some.host.name, as listed in \"Outgoing Calls\" above. It's \ + easiest to copy and paste the providers from above. Invalid entries, including providers not \ + enabled for outgoing calls, will be rejected silently. Entries can be made in a space-separated \ + list, and/or one per line by hitting enter after every one.") +end + + +s = m:section(NamedSection, "providers_user_can_use", "call_routing", + translate("Providers Used for Outgoing Calls"), text) +s.anonymous = true + +for k,v in pairs(validoutusers) do + providers = s:option(DynamicList, k, k) + + -- If the saved field is empty, we return a string telling the user + -- that this user uses all providers enavled for outgoing calls. + function providers.cfgvalue(self, section) + value = self.map:get(section, self.option) + + if value == nil then + return {translate("Uses providers enabled for outgoing calls")} + else + newvalue = {} + -- Convert internal names to user@host values. + for i,v in ipairs(value) do + newvalue[i] = validoutaccounts[v] + end + return newvalue + end + end + + -- Cook the new values prior to entering them into the config file. + -- Also, enter them only if they are valid. + function providers.write(self, section, value) + cookedvalue = {} + cindex = 1 + for index, field in ipairs(value) do + cooked = string.gsub(luci.util.trim(value[index]), "%W", "_") + if validoutaccounts[cooked] ~= nil then + cookedvalue[cindex] = cooked + cindex = cindex + 1 + end + end + DynamicList.write(self, section, cookedvalue) + end +end + +---------------------------------------------------------------------------------------------------- +s = m:section(TypedSection, "callthrough_numbers", translate("Call-through Numbers"), + translate("Designate numbers that are allowed to call through this system and which user's \ + privileges they will have.")) +s.anonymous = true +s.addremove = true + +num = s:option(DynamicList, "callthrough_number_list", translate("Call-through Numbers"), + translate("Specify numbers individually here. Press enter to add more numbers. \ + You will have to experiment with what country and area codes you need to add \ + to the number.")) +num.datatype = "uinteger" + +p = s:option(ListValue, "enabled", translate("Enabled")) +p:value("yes", translate("Yes")) +p:value("no", translate("No")) +p.default = "yes" + +user = s:option(Value, "defaultuser", translate("User Name"), + translate("The number(s) specified above will be able to dial out with this user's providers. \ + Invalid usernames, including users not enabled for outgoing calls, are dropped silently. \ + Please verify that the entry was accepted.")) +function user.write(self, section, value) + trimuser = luci.util.trim(value) + if allvalidusers[trimuser] == true then + Value.write(self, section, trimuser) + end +end + +pwd = s:option(Value, "pin", translate("PIN"), + translate("Your PIN disappears when saved for your protection. It will be changed \ + only when you enter a value different from the saved one. Leaving the PIN \ + empty is possible, but please beware of the security implications.")) +pwd.password = true +pwd.rmempty = false + +-- We skip reading off the saved value and return nothing. +function pwd.cfgvalue(self, section) + return "" +end + +-- We check the entered value against the saved one, and only write if the entered value is +-- something other than the empty string, and it differes from the saved value. +function pwd.write(self, section, value) + local orig_pwd = m:get(section, self.option) + if value and #value > 0 and orig_pwd ~= value then + Value.write(self, section, value) + end +end + +---------------------------------------------------------------------------------------------------- +s = m:section(TypedSection, "callback_numbers", translate("Call-back Numbers"), + translate("Designate numbers to whom the system will hang up and call back, which provider will \ + be used to call them, and which user's privileges will be granted to them.")) +s.anonymous = true +s.addremove = true + +num = s:option(DynamicList, "callback_number_list", translate("Call-back Numbers"), + translate("Specify numbers individually here. Press enter to add more numbers. \ + You will have to experiment with what country and area codes you need to add \ + to the number.")) +num.datatype = "uinteger" + +p = s:option(ListValue, "enabled", translate("Enabled")) +p:value("yes", translate("Yes")) +p:value("no", translate("No")) +p.default = "yes" + +delay = s:option(Value, "callback_hangup_delay", translate("Hang-up Delay"), + translate("How long to wait before hanging up. If the provider you use to dial automatically forwards \ + to voicemail, you can set this value to a delay that will allow you to hang up before your call gets \ + forwarded and you get billed for it.")) +delay.datatype = "uinteger" +delay.default = 0 + +user = s:option(Value, "defaultuser", translate("User Name"), + translate("The number(s) specified above will be able to dial out with this user's providers. \ + Invalid usernames, including users not enabled for outgoing calls, are dropped silently. \ + Please verify that the entry was accepted.")) +function user.write(self, section, value) + trimuser = luci.util.trim(value) + if allvalidusers[trimuser] == true then + Value.write(self, section, trimuser) + end +end + +pwd = s:option(Value, "pin", translate("PIN"), + translate("Your PIN disappears when saved for your protection. It will be changed \ + only when you enter a value different from the saved one. Leaving the PIN \ + empty is possible, but please beware of the security implications.")) +pwd.password = true +pwd.rmempty = false + +-- We skip reading off the saved value and return nothing. +function pwd.cfgvalue(self, section) + return "" +end + +-- We check the entered value against the saved one, and only write if the entered value is +-- something other than the empty string, and it differes from the saved value. +function pwd.write(self, section, value) + local orig_pwd = m:get(section, self.option) + if value and #value > 0 and orig_pwd ~= value then + Value.write(self, section, value) + end +end + +provider = s:option(Value, "callback_provider", translate("Call-back Provider"), + translate("Enter a VoIP provider to use for call-back in the format username@some.host.name, as listed in \ + \"Outgoing Calls\" above. It's easiest to copy and paste the providers from above. Invalid entries, including \ + providers not enabled for outgoing calls, will be rejected silently.")) +function provider.write(self, section, value) + cooked = string.gsub(luci.util.trim(value), "%W", "_") + if validoutaccounts[cooked] ~= nil then + Value.write(self, section, value) + end +end + +---------------------------------------------------------------------------------------------------- +s = m:section(NamedSection, "blacklisting", "call_routing", translate("Blacklisted Numbers"), + translate("Enter phone numbers that you want to decline calls from automatically. \ + You should probably omit the country code and any leading zeroes, but please \ + experiment to make sure you are blocking numbers from your desired area successfully.")) +s.anonymous = true + +b = s:option(DynamicList, "blacklist1", translate("Dynamic List of Blacklisted Numbers"), + translate("Specify numbers individually here. Press enter to add more numbers.")) +b.cast = "string" +b.datatype = "uinteger" + +b = s:option(Value, "blacklist2", translate("Space-Separated List of Blacklisted Numbers"), + translate("Copy-paste large lists of numbers here.")) +b.template = "cbi/tvalue" +b.rows = 3 + +return m diff --git a/applications/luci-app-pbx/luasrc/model/cbi/pbx-google.lua b/applications/luci-app-pbx/luasrc/model/cbi/pbx-google.lua new file mode 100644 index 0000000000..3c36a168d9 --- /dev/null +++ b/applications/luci-app-pbx/luasrc/model/cbi/pbx-google.lua @@ -0,0 +1,122 @@ +--[[ + Copyright 2011 Iordan Iordanov <iiordanov (AT) gmail.com> + + This file is part of luci-pbx. + + luci-pbx is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + luci-pbx is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with luci-pbx. If not, see <http://www.gnu.org/licenses/>. +]]-- + +if nixio.fs.access("/etc/init.d/asterisk") then + server = "asterisk" +elseif nixio.fs.access("/etc/init.d/freeswitch") then + server = "freeswitch" +else + server = "" +end + +modulename = "pbx-google" +googlemodulename = "pbx-google" +defaultstatus = "dnd" +defaultstatusmessage = "PBX online, may lose messages" + +m = Map (modulename, translate("Google Accounts"), + translate("This is where you set up your Google (Talk and Voice) Accounts, in order to start \ + using them for dialing and receiving calls (voice chat and real phone calls). Please \ + make at least one voice call using the Google Talk plugin installable through the \ + GMail interface, and then log out from your account everywhere. Click \"Add\" \ + to add as many accounts as you wish.")) + +-- Recreate the config, and restart services after changes are commited to the configuration. +function m.on_after_commit(self) + -- Create a field "name" for each account that identifies the account in the backend. + commit = false + m.uci:foreach(modulename, "gtalk_jabber", + function(s1) + if s1.username ~= nil then + name=string.gsub(s1.username, "%W", "_") + if s1.name ~= name then + m.uci:set(modulename, s1['.name'], "name", name) + commit = true + end + end + end) + if commit == true then m.uci:commit(modulename) end + + luci.sys.call("/etc/init.d/pbx-" .. server .. " restart 1\>/dev/null 2\>/dev/null") + luci.sys.call("/etc/init.d/asterisk restart 1\>/dev/null 2\>/dev/null") +end + +----------------------------------------------------------------------------- +s = m:section(TypedSection, "gtalk_jabber", translate("Google Voice/Talk Accounts")) +s.anonymous = true +s.addremove = true + +s:option(Value, "username", translate("Email")) + +pwd = s:option(Value, "secret", translate("Password"), + translate("When your password is saved, it disappears from this field and is not displayed \ + for your protection. The previously saved password will be changed only when you \ + enter a value different from the saved one.")) +pwd.password = true +pwd.rmempty = false + +-- We skip reading off the saved value and return nothing. +function pwd.cfgvalue(self, section) + return "" +end + +-- We check the entered value against the saved one, and only write if the entered value is +-- something other than the empty string, and it differes from the saved value. +function pwd.write(self, section, value) + local orig_pwd = m:get(section, self.option) + if value and #value > 0 and orig_pwd ~= value then + Value.write(self, section, value) + end +end + + +p = s:option(ListValue, "register", + translate("Enable Incoming Calls (set Status below)"), + translate("When somebody starts voice chat with your GTalk account or calls the GVoice, \ + number (if you have Google Voice), the call will be forwarded to any users \ + that are online (registered using a SIP device or softphone) and permitted to \ + receive the call. If you have Google Voice, you must go to your GVoice settings and \ + forward calls to Google chat in order to actually receive calls made to your \ + GVoice number. If you have trouble receiving calls from GVoice, experiment \ + with the Call Screening option in your GVoice Settings. Finally, make sure no other \ + client is online with this account (browser in gmail, mobile/desktop Google Talk \ + App) as it may interfere.")) +p:value("yes", translate("Yes")) +p:value("no", translate("No")) +p.default = "yes" + +p = s:option(ListValue, "make_outgoing_calls", translate("Enable Outgoing Calls"), + translate("Use this account to make outgoing calls as configured in the \"Call Routing\" section.")) +p:value("yes", translate("Yes")) +p:value("no", translate("No")) +p.default = "yes" + +st = s:option(ListValue, "status", translate("Google Talk Status")) +st:depends("register", "yes") +st:value("dnd", translate("Do Not Disturb")) +st:value("away", translate("Away")) +st:value("available", translate("Available")) +st.default = defaultstatus + +stm = s:option(Value, "statusmessage", translate("Google Talk Status Message"), + translate("Avoid using anything but alpha-numeric characters, space, comma, and period.")) +stm:depends("register", "yes") +stm.default = defaultstatusmessage + +return m diff --git a/applications/luci-app-pbx/luasrc/model/cbi/pbx-users.lua b/applications/luci-app-pbx/luasrc/model/cbi/pbx-users.lua new file mode 100644 index 0000000000..c7c8b4d8bb --- /dev/null +++ b/applications/luci-app-pbx/luasrc/model/cbi/pbx-users.lua @@ -0,0 +1,133 @@ +--[[ + Copyright 2011 Iordan Iordanov <iiordanov (AT) gmail.com> + + This file is part of luci-pbx. + + luci-pbx is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + luci-pbx is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with luci-pbx. If not, see <http://www.gnu.org/licenses/>. +]]-- + +if nixio.fs.access("/etc/init.d/asterisk") then + server = "asterisk" +elseif nixio.fs.access("/etc/init.d/freeswitch") then + server = "freeswitch" +else + server = "" +end + +modulename = "pbx-users" +modulenamecalls = "pbx-calls" +modulenameadvanced = "pbx-advanced" + + +m = Map (modulename, translate("User Accounts"), + translate("Here you must configure at least one SIP account, that you \ + will use to register with this service. Use this account either in an Analog Telephony \ + Adapter (ATA), or in a SIP software like CSipSimple, Linphone, or Sipdroid on your \ + smartphone, or Ekiga, Linphone, or X-Lite on your computer. By default, all SIP accounts \ + will ring simultaneously if a call is made to one of your VoIP provider accounts or GV \ + numbers.")) + +-- Recreate the config, and restart services after changes are commited to the configuration. +function m.on_after_commit(self) + luci.sys.call("/etc/init.d/pbx-" .. server .. " restart 1\>/dev/null 2\>/dev/null") + luci.sys.call("/etc/init.d/" .. server .. " restart 1\>/dev/null 2\>/dev/null") +end + +externhost = m.uci:get(modulenameadvanced, "advanced", "externhost") +bindport = m.uci:get(modulenameadvanced, "advanced", "bindport") +ipaddr = m.uci:get("network", "lan", "ipaddr") + +----------------------------------------------------------------------------- +s = m:section(NamedSection, "server", "user", translate("Server Setting")) +s.anonymous = true + +if ipaddr == nil or ipaddr == "" then + ipaddr = "(IP address not static)" +end + +if bindport ~= nil then + just_ipaddr = ipaddr + ipaddr = ipaddr .. ":" .. bindport +end + +s:option(DummyValue, "ipaddr", translate("Server Setting for Local SIP Devices"), + translate("Enter this IP (or IP:port) in the Server/Registrar setting of SIP devices you will \ + use ONLY locally and never from a remote location.")).default = ipaddr + +if externhost ~= nil then + if bindport ~= nil then + just_externhost = externhost + externhost = externhost .. ":" .. bindport + end + s:option(DummyValue, "externhost", translate("Server Setting for Remote SIP Devices"), + translate("Enter this hostname (or hostname:port) in the Server/Registrar setting of SIP \ + devices you will use from a remote location (they will work locally too).") + ).default = externhost +end + +if bindport ~= nil then + s:option(DummyValue, "bindport", translate("Port Setting for SIP Devices"), + translatef("If setting Server/Registrar to %s or %s does not work for you, try setting \ + it to %s or %s and entering this port number in a separate field that specifies the \ + Server/Registrar port number. Beware that some devices have a confusing \ + setting that sets the port where SIP requests originate from on the SIP \ + device itself (the bind port). The port specified on this page is NOT this bind port \ + but the port this service listens on.", + ipaddr, externhost, just_ipaddr, just_externhost)).default = bindport +end + +----------------------------------------------------------------------------- +s = m:section(TypedSection, "local_user", translate("SIP Device/Softphone Accounts")) +s.anonymous = true +s.addremove = true + +s:option(Value, "fullname", translate("Full Name"), + translate("You can specify a real name to show up in the Caller ID here.")) + +du = s:option(Value, "defaultuser", translate("User Name"), + translate("Use (four to five digit) numeric user name if you are connecting normal telephones \ + with ATAs to this system (so they can dial user names).")) +du.datatype = "uciname" + +pwd = s:option(Value, "secret", translate("Password"), + translate("Your password disappears when saved for your protection. It will be changed \ + only when you enter a value different from the saved one.")) +pwd.password = true +pwd.rmempty = false + +-- We skip reading off the saved value and return nothing. +function pwd.cfgvalue(self, section) + return "" +end + +-- We check the entered value against the saved one, and only write if the entered value is +-- something other than the empty string, and it differes from the saved value. +function pwd.write(self, section, value) + local orig_pwd = m:get(section, self.option) + if value and #value > 0 and orig_pwd ~= value then + Value.write(self, section, value) + end +end + +p = s:option(ListValue, "ring", translate("Receives Incoming Calls")) +p:value("yes", translate("Yes")) +p:value("no", translate("No")) +p.default = "yes" + +p = s:option(ListValue, "can_call", translate("Makes Outgoing Calls")) +p:value("yes", translate("Yes")) +p:value("no", translate("No")) +p.default = "yes" + +return m diff --git a/applications/luci-app-pbx/luasrc/model/cbi/pbx-voip.lua b/applications/luci-app-pbx/luasrc/model/cbi/pbx-voip.lua new file mode 100644 index 0000000000..ed1ed1edb1 --- /dev/null +++ b/applications/luci-app-pbx/luasrc/model/cbi/pbx-voip.lua @@ -0,0 +1,116 @@ +--[[ + Copyright 2011 Iordan Iordanov <iiordanov (AT) gmail.com> + + This file is part of luci-pbx. + + luci-pbx is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + luci-pbx is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with luci-pbx. If not, see <http://www.gnu.org/licenses/>. +]]-- + +if nixio.fs.access("/etc/init.d/asterisk") then + server = "asterisk" +elseif nixio.fs.access("/etc/init.d/freeswitch") then + server = "freeswitch" +else + server = "" +end + +modulename = "pbx-voip" + +m = Map (modulename, translate("SIP Accounts"), + translate("This is where you set up your SIP (VoIP) accounts ts like Sipgate, SipSorcery, \ + the popular Betamax providers, and any other providers with SIP settings in order to start \ + using them for dialing and receiving calls (SIP uri and real phone calls). Click \"Add\" to \ + add as many accounts as you wish.")) + +-- Recreate the config, and restart services after changes are commited to the configuration. +function m.on_after_commit(self) + commit = false + -- Create a field "name" for each account that identifies the account in the backend. + m.uci:foreach(modulename, "voip_provider", + function(s1) + if s1.defaultuser ~= nil and s1.host ~= nil then + name=string.gsub(s1.defaultuser.."_"..s1.host, "%W", "_") + if s1.name ~= name then + m.uci:set(modulename, s1['.name'], "name", name) + commit = true + end + end + end) + if commit == true then m.uci:commit(modulename) end + + luci.sys.call("/etc/init.d/pbx-" .. server .. " restart 1\>/dev/null 2\>/dev/null") + luci.sys.call("/etc/init.d/" .. server .. " restart 1\>/dev/null 2\>/dev/null") +end + +----------------------------------------------------------------------------- +s = m:section(TypedSection, "voip_provider", translate("SIP Provider Accounts")) +s.anonymous = true +s.addremove = true + +s:option(Value, "defaultuser", translate("User Name")) +pwd = s:option(Value, "secret", translate("Password"), + translate("When your password is saved, it disappears from this field and is not displayed \ + for your protection. The previously saved password will be changed only when you \ + enter a value different from the saved one.")) + + + +pwd.password = true +pwd.rmempty = false + +-- We skip reading off the saved value and return nothing. +function pwd.cfgvalue(self, section) + return "" +end + +-- We check the entered value against the saved one, and only write if the entered value is +-- something other than the empty string, and it differes from the saved value. +function pwd.write(self, section, value) + local orig_pwd = m:get(section, self.option) + if value and #value > 0 and orig_pwd ~= value then + Value.write(self, section, value) + end +end + +h = s:option(Value, "host", translate("SIP Server/Registrar")) +h.datatype = "host" + +p = s:option(ListValue, "register", translate("Enable Incoming Calls (Register via SIP)"), + translate("This option should be set to \"Yes\" if you have a DID \(real telephone number\) \ + associated with this SIP account or want to receive SIP uri calls through this \ + provider.")) +p:value("yes", translate("Yes")) +p:value("no", translate("No")) +p.default = "yes" + +p = s:option(ListValue, "make_outgoing_calls", translate("Enable Outgoing Calls"), + translate("Use this account to make outgoing calls.")) +p:value("yes", translate("Yes")) +p:value("no", translate("No")) +p.default = "yes" + +from = s:option(Value, "fromdomain", + translate("SIP Realm (needed by some providers)")) +from.optional = true +from.datatype = "host" + +port = s:option(Value, "port", translate("SIP Server/Registrar Port")) +port.optional = true +port.datatype = "port" + +op = s:option(Value, "outboundproxy", translate("Outbound Proxy")) +op.optional = true +op.datatype = "host" + +return m diff --git a/applications/luci-app-pbx/luasrc/model/cbi/pbx.lua b/applications/luci-app-pbx/luasrc/model/cbi/pbx.lua new file mode 100644 index 0000000000..4c5fcbdecd --- /dev/null +++ b/applications/luci-app-pbx/luasrc/model/cbi/pbx.lua @@ -0,0 +1,115 @@ +--[[ + Copyright 2011 Iordan Iordanov <iiordanov (AT) gmail.com> + + This file is part of luci-pbx. + + luci-pbx is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + luci-pbx is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with luci-pbx. If not, see <http://www.gnu.org/licenses/>. +]]-- + +modulename = "pbx" + + +if nixio.fs.access("/etc/init.d/asterisk") then + server = "asterisk" +elseif nixio.fs.access("/etc/init.d/freeswitch") then + server = "freeswitch" +else + server = "" +end + + +-- Returns formatted output of string containing only the words at the indices +-- specified in the table "indices". +function format_indices(string, indices) + if indices == nil then + return "Error: No indices to format specified.\n" + end + + -- Split input into separate lines. + lines = luci.util.split(luci.util.trim(string), "\n") + + -- Split lines into separate words. + splitlines = {} + for lpos,line in ipairs(lines) do + splitlines[lpos] = luci.util.split(luci.util.trim(line), "%s+", nil, true) + end + + -- For each split line, if the word at all indices specified + -- to be formatted are not null, add the formatted line to the + -- gathered output. + output = "" + for lpos,splitline in ipairs(splitlines) do + loutput = "" + for ipos,index in ipairs(indices) do + if splitline[index] ~= nil then + loutput = loutput .. string.format("%-40s", splitline[index]) + else + loutput = nil + break + end + end + + if loutput ~= nil then + output = output .. loutput .. "\n" + end + end + return output +end + + +m = Map (modulename, translate("PBX Main Page"), + translate("This configuration page allows you to configure a phone system (PBX) service which \ + permits making phone calls through multiple Google and SIP (like Sipgate, \ + SipSorcery, and Betamax) accounts and sharing them among many SIP devices. \ + Note that Google accounts, SIP accounts, and local user accounts are configured in the \ + \"Google Accounts\", \"SIP Accounts\", and \"User Accounts\" sub-sections. \ + You must add at least one User Account to this PBX, and then configure a SIP device or \ + softphone to use the account, in order to make and receive calls with your Google/SIP \ + accounts. Configuring multiple users will allow you to make free calls between all users, \ + and share the configured Google and SIP accounts. If you have more than one Google and SIP \ + accounts set up, you should probably configure how calls to and from them are routed in \ + the \"Call Routing\" page. If you're interested in using your own PBX from anywhere in the \ + world, then visit the \"Remote Usage\" section in the \"Advanced Settings\" page.")) + +----------------------------------------------------------------------------------------- +s = m:section(NamedSection, "connection_status", "main", + translate("PBX Service Status")) +s.anonymous = true + +s:option (DummyValue, "status", translate("Service Status")) + +sts = s:option(DummyValue, "_sts") +sts.template = "cbi/tvalue" +sts.rows = 20 + +function sts.cfgvalue(self, section) + + if server == "asterisk" then + regs = luci.sys.exec("asterisk -rx 'sip show registry' | sed 's/peer-//'") + jabs = luci.sys.exec("asterisk -rx 'jabber show connections' | grep onnected") + usrs = luci.sys.exec("asterisk -rx 'sip show users'") + chan = luci.sys.exec("asterisk -rx 'core show channels'") + + return format_indices(regs, {1, 5}) .. + format_indices(jabs, {2, 4}) .. "\n" .. + format_indices(usrs, {1} ) .. "\n" .. chan + + elseif server == "freeswitch" then + return "Freeswitch is not supported yet.\n" + else + return "Neither Asterisk nor FreeSwitch discovered, please install Asterisk, as Freeswitch is not supported yet.\n" + end +end + +return m |