diff options
author | Ted Trask <ttrask01@yahoo.com> | 2011-10-10 19:34:59 +0000 |
---|---|---|
committer | Ted Trask <ttrask01@yahoo.com> | 2011-10-10 19:34:59 +0000 |
commit | 1d895ace660869053830ed33302e0708efb6065e (patch) | |
tree | 0ed844a6a408042ec0c6f4a11726db7a7915881b | |
parent | b802504e41cccda64b8f903973d01897bbec7451 (diff) | |
download | acf-provisioning-1d895ace660869053830ed33302e0708efb6065e.tar.bz2 acf-provisioning-1d895ace660869053830ed33302e0708efb6065e.tar.xz |
Added in configuration files / scripts and cgi-bin scripts.
-rw-r--r-- | Makefile | 15 | ||||
-rwxr-xr-x | cgi-bin/provisioning.cgi | 165 | ||||
-rwxr-xr-x | cgi-bin/redirect.cgi | 3 | ||||
-rw-r--r-- | config/acl.conf | 2 | ||||
-rw-r--r-- | config/delete_device.lua | 44 | ||||
-rw-r--r-- | config/determine_class.lua | 21 | ||||
-rw-r--r-- | config/lighttpd.sample.conf | 338 | ||||
-rwxr-xr-x | config/notify_device | 30 | ||||
-rw-r--r-- | config/templates/linksysata-template.lua (renamed from templates/linksysata-template.lua) | 0 | ||||
-rw-r--r-- | config/templates/polycom-template.lua (renamed from templates/polycom-template.lua) | 0 | ||||
-rw-r--r-- | config/templates/snom-template.lua (renamed from templates/snom-template.lua) | 0 | ||||
-rw-r--r-- | config/update_device.lua | 14 | ||||
-rw-r--r-- | config/update_device_params.lua | 41 |
13 files changed, 668 insertions, 5 deletions
@@ -5,18 +5,21 @@ VERSION=0.0.10 APP_DIST=\ provisioning* \ -TEMPLATE_DIST = templates/* +CONFIG_DIST = config/* + +CGIBIN_DIST = cgi-bin/* EXTRA_DIST=README Makefile config.mk -DISTFILES=$(APP_DIST) $(EXTRA_DIST) $(TEMPLATE_DIST) +DISTFILES=$(APP_DIST) $(EXTRA_DIST) $(CONFIG_DIST) $(CGIBIN_DIST) TAR=tar P=$(PACKAGE)-$(VERSION) tarball=$(P).tar.bz2 install_dir=$(DESTDIR)/$(appdir)/$(APP_NAME) -template_dir=$(DESTDIR)/etc/provisioning/templates +config_dir=$(DESTDIR)/etc/provisioning +cgibin_dir=$(DESTDIR)/var/www/provisioning/htdocs/cgi-bin all: clean: @@ -27,8 +30,10 @@ dist: $(tarball) install: mkdir -p "$(install_dir)" cp -a $(APP_DIST) "$(install_dir)" - mkdir -p "$(template_dir)" - cp -a $(TEMPLATE_DIST) "$(template_dir)" + mkdir -p "$(config_dir)" + cp -a $(CONFIG_DIST) "$(config_dir)" + mkdir -p "$(cgibin_dir)" + cp -a $(CGIBIN_DIST) "$(cgibin_dir)" $(tarball): $(DISTFILES) rm -rf $(P) diff --git a/cgi-bin/provisioning.cgi b/cgi-bin/provisioning.cgi new file mode 100755 index 0000000..bf7a6a2 --- /dev/null +++ b/cgi-bin/provisioning.cgi @@ -0,0 +1,165 @@ +#!/usr/bin/haserl --shell=lua + +<% +require "posix" + +local path_info = ENV["PATH_INFO"] or ENV["REQUEST_URI"] or "" +local root = ENV["DOCUMENT_ROOT"] or "" +local request_method = ENV["REQUEST_METHOD"] or "" +local user_agent = ENV["HTTP_USER_AGENT"] or "" +local ip_address = ENV["REMOTE_ADDR"] or "" + +local log = io.open("/var/log/provisioning.log", "a+") +log:write("Running provisioning.cgi ".. os.date() .. "\n") +log:write("Processing a "..request_method.." on "..path_info.."\n") +-- for a,b in pairs(ENV) do log:write(a.."=", b,"\n") end + +local ACL_FILE = "/etc/provisioning/acl.conf" + +function acl_check (...) + local allowed = false + local f = io.open(ACL_FILE) + if f then + for line in f:lines() do + if string.match(user_agent, line) or string.match(ip_address, line) then + log:write("ALLOW MATCH FOUND " .. line .. "\n") + allowed = true + break + end + end + f:close() + end + if not allowed then + log:write("ALLOW MATCH NOT FOUND. ACCESS DENIED\n") + end + return allowed +end + +function http_code (code) + log:write("HTTP code processed " .. code .. "\n") + if code == 200 then + io.stdout:write("Status: 200 OK\n") + io.stdout:write("Content-Type: \n\n") + elseif code == 204 then + io.stdout:write("Status: 204 No Content\n") + io.stdout:write("Content-Type: \n\n") + elseif code == 404 then + io.stdout:write("Status: 404 Not Found\n") + io.stdout:write("Content-Type: \n\n") + io.stdout:write("Status: 404 Not Found\n") + io.stdout:write("\n") + elseif code == 403 then + io.stdout:write("Status: 403 Forbidden\n") + io.stdout:write("Content-Type: \n\n") + io.stdout:write("Status: 403 Forbidden\n") + io.stdout:write("\n") + else + io.stdout:write("Status: 400 Bad Request\n") + io.stdout:write("Content-Type: \n\n") + io.stdout:write("Status: 400 Bad Request\n") + io.stdout:write("\n") + end +end + +-------------------------------------------------------------------------------------------------------------- +-- Code Begins Here -- +-------------------------------------------------------------------------------------------------------------- +if not acl_check() then + http_code(403) + log:close() + os.exit() +end + +local basename = posix.basename(path_info) +local mac = string.match(basename, "%x%x%x%x%x%x%x%x%x%x%x%x") + +if ( request_method == "GET" ) then + -- don't even bother for files that don't contain a MAC + if mac == nil then + http_code(404) + log:close() + os.exit() + end + + -- If it's a Polycom, 404 the MAC.cfg, MAC-directory.xml, MAC-license, MAC-phone.cfg, and MAC-web.cfg files + local f = string.match(basename, mac.."(.*)") + if string.match(user_agent, "Polycom") and (f==".cfg" or f=="-directory.xml" or f=="-license.cfg" or f=="-phone.cfg" or f=="-web.cfg" or mac=="000000000000") then + http_code(404) + log:close() + os.exit() + end + + log:write("Checking PROV Table for results\n") + -- Load the ACF mvc + local PATH = package.path + package.path = "/usr/share/acf/www/cgi-bin/?.lua;" .. package.path + require("mvc") + package.path = PATH + -- We'll use the cli controller, but change the view resolver to call the template + local pathinfo = ENV.PATH_INFO + FRAMEWORK=mvc:new() + FRAMEWORK:read_config("acf") + APP=FRAMEWORK:new("acf_cli") + APP.view_resolver = function(self) + return function (data) + if not data.errtxt and data.value.values and data.value.values.value.device and data.value.values.value.device.template then + local func = haserl.loadfile(data.value.values.value.device.template) + func(data.value.values.value) + else + if data.errtxt then + APP.logevent("data.errtxt") + elseif not data.value.values then + APP.logevent("not data.value.values") + elseif not data.value.values.value.device then + APP.logevent("not data.value.values.value.device") + elseif not data.value.values.value.device.template then + APP.logevent("not data.value.values.value.device.template") + end + http_code(404) + end + end + end + -- Set up the action and parameters + ENV.PATH_INFO = "/provisioning/provisioning/getfile" + APP.clientdata = {mac=mac, ip=ip_address, agent=user_agent} + -- Dispatch the command + APP:dispatch() + APP:destroy() + FRAMEWORK:destroy() + + ENV.PATH_INFO = pathinfo +elseif ( request_method == "PUT" ) then + local data = io.stdin:read("*all") + + log:write("Checking PROV Table for results\n") + -- Load the ACF mvc + local PATH = package.path + package.path = "/usr/share/acf/www/cgi-bin/?.lua;" .. package.path + require("mvc") + package.path = PATH + -- We'll use the cli controller, but change the view resolver to report HTTP code + local pathinfo = ENV.PATH_INFO + FRAMEWORK=mvc:new() + FRAMEWORK:read_config("acf") + APP=FRAMEWORK:new("acf_cli") + APP.view_resolver = function(self) + return function (data) + if data.errtxt then + http_code(400) + else + http_code(200) + end + end + end + -- Set up the action and parameters + ENV.PATH_INFO = "/provisioning/provisioning/putfile" + APP.clientdata = {file=path_info, root=root, data=data, ip=ip_address, agent=user_agent} + -- Dispatch the command + APP:dispatch() + APP:destroy() + FRAMEWORK:destroy() + + ENV.PATH_INFO = pathinfo +end +log:close() +%> diff --git a/cgi-bin/redirect.cgi b/cgi-bin/redirect.cgi new file mode 100755 index 0000000..5bc91cc --- /dev/null +++ b/cgi-bin/redirect.cgi @@ -0,0 +1,3 @@ +#!/bin/sh +echo "Location: https://$HTTP_HOST" +echo diff --git a/config/acl.conf b/config/acl.conf new file mode 100644 index 0000000..9469c42 --- /dev/null +++ b/config/acl.conf @@ -0,0 +1,2 @@ +Linksys +Polycom diff --git a/config/delete_device.lua b/config/delete_device.lua new file mode 100644 index 0000000..ad908ed --- /dev/null +++ b/config/delete_device.lua @@ -0,0 +1,44 @@ +-- This is the script run after deleting a device (and all of its params) +local functions, olddevice, oldparams = ... + +local path = "PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin " + +APP.logevent("got to delete_device script") + +-- We'll handle the deleting of the device by handling the resulting changing of the params +-- First, have to create a new set of params (with blank extensions) +local duplicatestructure +duplicatestructure = function(value, saved) + saved = saved or {} + if type(value) == "table" then + if saved[value] then + return saved[value] + else + local output = {} + saved[value] = output + for k,v in pairs(value) do + output[k] = duplicatestructure(v, saved) + end + return output + end + else + return value + end +end + +local params = duplicatestructure(oldparams) +for name,val in pairs(params.value) do + if string.match(name, "^reg") then + params.value[name].value.extension.value = "" + end +end + +-- Then call the other script +local env = {} +setmetatable (env, {__index = _G}) +-- loadfile loads into the global environment +-- so we set env 0, not env 1 +setfenv (0, env) +local f = loadfile("/etc/provisioning/update_device_params.lua") +if (f) then f(functions, params, oldparams) end +setfenv (0, _G) diff --git a/config/determine_class.lua b/config/determine_class.lua new file mode 100644 index 0000000..df8f5c2 --- /dev/null +++ b/config/determine_class.lua @@ -0,0 +1,21 @@ +-- This is the script run to determine the device class from the HTTP user agent +local functions, agent, classes = ... + +APP.logevent("got to determine_class script") + +local manufacture, model + +if string.match(agent, "Polycom") then + manufacture = "Polycom" + model = string.match(agent, "Polycom[^%s%d]+(%d+)") +end + +APP.logevent("Found "..(manufacture or "").." model "..(model or "")) + +if manufacture and model then + for i,c in ipairs(classes.value) do + if string.match(c.label, manufacture) and string.match(c.label, model) then + return c.class_id + end + end +end diff --git a/config/lighttpd.sample.conf b/config/lighttpd.sample.conf new file mode 100644 index 0000000..23dbe0d --- /dev/null +++ b/config/lighttpd.sample.conf @@ -0,0 +1,338 @@ +############################################################################### +# Default lighttpd.conf for Gentoo. +# $Header: /var/cvsroot/gentoo-x86/www-servers/lighttpd/files/conf/lighttpd.conf,v 1.3 2005/09/01 14:22:35 ka0ttic Exp $ +############################################################################### + +server.reject-expect-100-with-417 = "disable" +debug.log-request-handling = "enable" +debug.log-file-not-found = "enable" +debug.log-request-header-on-error = "enable" + +# {{{ variables +var.basedir = "/var/www/provisioning" +var.logdir = "/var/log/lighttpd" +var.statedir = "/var/lib/lighttpd" +# }}} + +# {{{ modules +# At the very least, mod_access and mod_accesslog should be enabled. +# All other modules should only be loaded if necessary. +# NOTE: the order of modules is important. +server.modules = ( + "mod_rewrite", +# "mod_redirect", +# "mod_alias", + "mod_access", +# "mod_cml", +# "mod_trigger_b4_dl", +# "mod_auth", +# "mod_status", +# "mod_setenv", +# "mod_proxy", +# "mod_simple_vhost", +# "mod_evhost", +# "mod_userdir", +# "mod_compress", +# "mod_ssi", +# "mod_usertrack", +# "mod_expire", +# "mod_secdownload", +# "mod_rrdtool", +# "mod_webdav", + "mod_accesslog" +) +# }}} + +# {{{ includes +include "mime-types.conf" +# uncomment for cgi support +include "mod_cgi.conf" +# uncomment for php/fastcgi support +# include "mod_fastcgi.conf" +# }}} + +# {{{ server settings +server.username = "lighttpd" +server.groupname = "lighttpd" + +server.document-root = var.basedir + "/htdocs" +server.pid-file = "/var/run/lighttpd.pid" + +server.errorlog = var.logdir + "/error.log" +# log errors to syslog instead +# server.errorlog-use-syslog = "enable" + +server.indexfiles = ("index.php", "index.html", + "index.htm", "default.htm") + +# server.tag = "lighttpd" + +server.follow-symlink = "enable" + +# event handler (defaults to "poll") +# see performance.txt +# +# for >= linux-2.4 +# server.event-handler = "linux-rtsig" +# for >= linux-2.6 +# server.event-handler = "linux-sysepoll" +# for FreeBSD +# server.event-handler = "freebsd-kqueue" + +# chroot to directory (defaults to no chroot) +# server.chroot = "/" + +# bind to port (defaults to 80) +# server.port = 81 + +# bind to name (defaults to all interfaces) +# server.bind = "grisu.home.kneschke.de" + +# error-handler for status 404 +# server.error-handler-404 = "/error-handler.html" +# server.error-handler-404 = "/error-handler.php" +server.error-handler-404 = "/cgi-bin/provisioning.cgi" + +# Format: <errorfile-prefix><status-code>.html +# -> ..../status-404.html for 'File not found' +# server.errorfile-prefix = var.basedir + "/error/status-" + +# FAM support for caching stat() calls +# requires that lighttpd be built with USE=fam +# server.stat-cache-engine = "fam" +# }}} + +# {{{ mod_staticfile + +# which extensions should not be handled via static-file transfer +# (extensions that are usually handled by mod_cgi, mod_fastcgi, etc). +static-file.exclude-extensions = (".php", ".pl", ".cgi", ".fcgi") +# }}} + +# {{{ mod_accesslog +accesslog.filename = var.logdir + "/access.log" +# }}} + +# {{{ mod_dirlisting +# enable directory listings +# dir-listing.activate = "enable" +# +# don't list hidden files/directories +# dir-listing.hide-dotfiles = "enable" +# +# use a different css for directory listings +# dir-listing.external-css = "/path/to/dir-listing.css" +# +# list of regular expressions. files that match any of the +# specified regular expressions will be excluded from directory +# listings. +# dir-listing.exclude = ("^\.", "~$") +# }}} + +# {{{ mod_access +# see access.txt + +url.access-deny = ("~", ".inc") +# }}} + +# {{{ mod_userdir +# see userdir.txt +# +# userdir.path = "public_html" +# userdir.exclude-user = ("root") +# }}} + +# {{{ mod_ssi +# see ssi.txt +# +# ssi.extension = (".shtml") +# }}} + +# {{{ mod_ssl +# see ssl.txt +# +# ssl.engine = "enable" +# ssl.pemfile = "server.pem" +# }}} + +# {{{ mod_status +# see status.txt +# +# status.status-url = "/server-status" +# status.config-url = "/server-config" +# }}} + +# {{{ mod_simple_vhost +# see simple-vhost.txt +# +# If you want name-based virtual hosting add the next three settings and load +# mod_simple_vhost +# +# document-root = +# virtual-server-root + virtual-server-default-host + virtual-server-docroot +# or +# virtual-server-root + http-host + virtual-server-docroot +# +# simple-vhost.server-root = "/home/weigon/wwwroot/servers/" +# simple-vhost.default-host = "grisu.home.kneschke.de" +# simple-vhost.document-root = "/pages/" +# }}} + +# {{{ mod_compress +# see compress.txt +# +# compress.cache-dir = var.statedir + "/cache/compress" +# compress.filetype = ("text/plain", "text/html") +# }}} + +# {{{ mod_proxy +# see proxy.txt +# +# proxy.server = ( ".php" => +# ( "localhost" => +# ( +# "host" => "192.168.0.101", +# "port" => 80 +# ) +# ) +# ) +# }}} + +# {{{ mod_auth +# see authentication.txt +# +# auth.backend = "plain" +# auth.backend.plain.userfile = "lighttpd.user" +# auth.backend.plain.groupfile = "lighttpd.group" + +# auth.backend.ldap.hostname = "localhost" +# auth.backend.ldap.base-dn = "dc=my-domain,dc=com" +# auth.backend.ldap.filter = "(uid=$)" + +# auth.require = ( "/server-status" => +# ( +# "method" => "digest", +# "realm" => "download archiv", +# "require" => "user=jan" +# ), +# "/server-info" => +# ( +# "method" => "digest", +# "realm" => "download archiv", +# "require" => "valid-user" +# ) +# ) +# }}} + +# {{{ mod_rewrite +# see rewrite.txt +# +# url.rewrite = ( +# "^/$" => "/server-status" +# ) +# }}} + +# {{{ mod_redirect +# see redirect.txt +# +# url.redirect = ( +# "^/wishlist/(.+)" => "http://www.123.org/$1" +# ) +# }}} + +# {{{ mod_evhost +# define a pattern for the host url finding +# %% => % sign +# %0 => domain name + tld +# %1 => tld +# %2 => domain name without tld +# %3 => subdomain 1 name +# %4 => subdomain 2 name +# +# evhost.path-pattern = "/home/storage/dev/www/%3/htdocs/" +# }}} + +# {{{ mod_expire +# expire.url = ( +# "/buggy/" => "access 2 hours", +# "/asdhas/" => "access plus 1 seconds 2 minutes" +# ) +# }}} + +# {{{ mod_rrdtool +# see rrdtool.txt +# +# rrdtool.binary = "/usr/bin/rrdtool" +# rrdtool.db-name = var.statedir + "/lighttpd.rrd" +# }}} + +# {{{ mod_setenv +# see setenv.txt +# +# setenv.add-request-header = ( "TRAV_ENV" => "mysql://user@host/db" ) +# setenv.add-response-header = ( "X-Secret-Message" => "42" ) +# }}} + +# {{{ mod_trigger_b4_dl +# see trigger_b4_dl.txt +# +# trigger-before-download.gdbm-filename = "/home/weigon/testbase/trigger.db" +# trigger-before-download.memcache-hosts = ( "127.0.0.1:11211" ) +# trigger-before-download.trigger-url = "^/trigger/" +# trigger-before-download.download-url = "^/download/" +# trigger-before-download.deny-url = "http://127.0.0.1/index.html" +# trigger-before-download.trigger-timeout = 10 +# }}} + +# {{{ mod_cml +# see cml.txt +# +# don't forget to add index.cml to server.indexfiles +# cml.extension = ".cml" +# cml.memcache-hosts = ( "127.0.0.1:11211" ) +# }}} + +# {{{ mod_webdav +# see webdav.txt +# +# $HTTP["url"] =~ "^/dav($|/)" { +# webdav.activate = "enable" +# webdav.is-readonly = "enable" +# } +# }}} + +# {{{ extra rules +# +# set Content-Encoding and reset Content-Type for browsers that +# support decompressing on-thy-fly (requires mod_setenv) +# $HTTP["url"] =~ "\.gz$" { +# setenv.add-response-header = ("Content-Encoding" => "x-gzip") +# mimetype.assign = (".gz" => "text/plain") +# } + +# $HTTP["url"] =~ "\.bz2$" { +# setenv.add-response-header = ("Content-Encoding" => "x-bzip2") +# mimetype.assign = (".bz2" => "text/plain") +# } +# +# }}} + +# {{{ debug +# debug.log-request-header = "enable" +# debug.log-response-header = "enable" +# debug.log-request-handling = "enable" +# debug.log-file-not-found = "enable" +# }}} + +# vim: set ft=conf foldmethod=marker et : + +url.rewrite = ( + "^/?$" => "/cgi-bin/redirect.cgi", +) + +$HTTP["request-method"] =~ "^(PUT|DELETE)$" { +url.rewrite = ( + "^(.*)$" => "/cgi-bin/provisioning.cgi/$1" +) +} + diff --git a/config/notify_device b/config/notify_device new file mode 100755 index 0000000..7d5ff2a --- /dev/null +++ b/config/notify_device @@ -0,0 +1,30 @@ +#!/usr/bin/lua +require("socket") +require("validator") + +if not arg or #arg == 0 then + print("Error - IP Address must be specified") + return 1 +end + +local address = arg[1] +local user = arg[2] or "" +if not validator.is_ipv4(address) then + print("Error - Invalid IP Address") + return 1 +end + +local msg = "NOTIFY sip:"..user.."@"..address..":5060 SIP/2.0\n".. +"Via: SIP/2.0/UDP 127.0.0.1\n".. +"From: <sip:provisioning@127.0.0.1>\n".. +"To: <sip:"..user.."@"..address..">\n".. +"Event: check-sync\n".. +"Date: "..os.date().."\n".. +"Call-ID: "..os.time().."msgto0@"..address.."\n".. +"CSeq: 102 NOTIFY\n".. +"Contact: <sip:provisioning@127.0.0.1>\n".. +"Content-Length: 0\n\n" + +local s = assert(socket.udp()) +assert(s:sendto(msg, address, 5060)) +s:close() diff --git a/templates/linksysata-template.lua b/config/templates/linksysata-template.lua index ba71657..ba71657 100644 --- a/templates/linksysata-template.lua +++ b/config/templates/linksysata-template.lua diff --git a/templates/polycom-template.lua b/config/templates/polycom-template.lua index 5ed35a3..5ed35a3 100644 --- a/templates/polycom-template.lua +++ b/config/templates/polycom-template.lua diff --git a/templates/snom-template.lua b/config/templates/snom-template.lua index 7ac4542..7ac4542 100644 --- a/templates/snom-template.lua +++ b/config/templates/snom-template.lua diff --git a/config/update_device.lua b/config/update_device.lua new file mode 100644 index 0000000..90f5340 --- /dev/null +++ b/config/update_device.lua @@ -0,0 +1,14 @@ +-- This is the script run after editing a device - the label, classes +local functions, device, olddevice, params, oldparams = ... + +APP.logevent("got to update_device script") + +-- We'll handle the changing of the device by handling the resulting changing of the params +local env = {} +setmetatable (env, {__index = _G}) +-- loadfile loads into the global environment +-- so we set env 0, not env 1 +setfenv (0, env) +local f = loadfile("/etc/provisioning/update_device_params.lua") +if (f) then f(functions, params, oldparams) end +setfenv (0, _G) diff --git a/config/update_device_params.lua b/config/update_device_params.lua new file mode 100644 index 0000000..974f339 --- /dev/null +++ b/config/update_device_params.lua @@ -0,0 +1,41 @@ +-- This is the script run after editing device params +local functions, params, oldparams = ... + +APP.logevent("got to update_device_params script") + +local function findip(mac) + if not mac or mac == "" then + return nil + end + local ipaddr = functions.getselectresponse("SELECT ip FROM provisioning_requests WHERE mac~*'"..mac.."'") + if ipaddr and ipaddr[1] then + return ipaddr[1].ip + end +end + +local notify_device = function(mac, extension) + local ipaddr = findip(mac) + if ipaddr then + APP.logevent("Notifying "..ipaddr.." to update for "..(mac or "")) + os.execute("/etc/provisioning/notify_device "..ipaddr.." "..extension) + addfuturenotify(ipaddr, extension) + else + APP.logevent("Warning - could not find IP address for "..(mac or "")) + end +end + +-- Notify the phone to pull it's config +-- Try to get a valid extension currently on the device +local oldexten = "" +for name,val in pairs(oldparams.value) do + if string.match(name, "^reg") and val.value.extension and val.value.extension.value ~= "" then + oldexten = val.value.extension.value + break + end +end +if params.value.device and params.value.device.value.mac and params.value.device.value.mac.value ~= "" then + notify_device(params.value.device.value.mac.value, oldexten) +end +if oldparams.value.device and oldparams.value.device.value.mac and oldparams.value.device.value.mac.value ~= "" then + notify_device(oldparams.value.device.value.mac.value, oldexten) +end |