From: Steven Barth Date: Wed, 6 Aug 2008 21:40:48 +0000 (+0000) Subject: libs/core: Outsourced luci.sys to own directory X-Git-Tag: 0.8.0~517 X-Git-Url: https://git.librecmc.org/?a=commitdiff_plain;h=c9a29250557d1891354257433fd3c6c49ef68f84;p=oweals%2Fluci.git libs/core: Outsourced luci.sys to own directory --- diff --git a/contrib/package/luci/Makefile b/contrib/package/luci/Makefile index edd8949d0..356d8050d 100644 --- a/contrib/package/luci/Makefile +++ b/contrib/package/luci/Makefile @@ -187,9 +187,19 @@ define Package/luci-ipkg/install endef +define Package/luci-sys + $(call Package/luci/libtemplate) + TITLE:=LuCI Linux/POSIX system library +endef + +define Package/luci-sys/install + $(call Package/luci/install/template,$(1),libs/sys) +endef + + define Package/luci-web $(call Package/luci/libtemplate) - DEPENDS+=+luci-http +luci-addons +luci-uci +luci-sgi-cgi + DEPENDS+=+luci-http +luci-sys +luci-addons +luci-uci +luci-sgi-cgi TITLE:=MVC Webframework endef @@ -560,6 +570,9 @@ endif ifneq ($(CONFIG_PACKAGE_luci-uci),) PKG_SELECTED_MODULES+=libs/uci endif +ifneq ($(CONFIG_PACKAGE_luci-sys),) + PKG_SELECTED_MODULES+=libs/sys +endif ifneq ($(CONFIG_PACKAGE_luci-web),) PKG_SELECTED_MODULES+=libs/web endif @@ -668,6 +681,7 @@ $(eval $(call BuildPackage,luci-fastindex)) $(eval $(call BuildPackage,luci-http)) $(eval $(call BuildPackage,luci-ipkg)) $(eval $(call BuildPackage,luci-uci)) +$(eval $(call BuildPackage,luci-sys)) $(eval $(call BuildPackage,luci-web)) $(eval $(call BuildPackage,luci-httpd)) diff --git a/libs/core/luasrc/sys.lua b/libs/core/luasrc/sys.lua deleted file mode 100644 index 14a2f294f..000000000 --- a/libs/core/luasrc/sys.lua +++ /dev/null @@ -1,493 +0,0 @@ ---[[ -LuCI - System library - -Description: -Utilities for interaction with the Linux system - -FileId: -$Id$ - -License: -Copyright 2008 Steven Barth - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. - -]]-- - ---- LuCI Linux and POSIX system utilities. -module("luci.sys", package.seeall) -require("posix") -require("luci.bits") -require("luci.util") -require("luci.fs") - ---- Invoke the luci-flash executable to write an image to the flash memory. --- @param kpattern Pattern of files to keep over flash process --- @return Return value of os.execute() -function flash(image, kpattern) - local cmd = "luci-flash " - if kpattern then - cmd = cmd .. "-k '" .. kpattern:gsub("'", "") .. "' " - end - cmd = cmd .. "'" .. image:gsub("'", "") .. "' >/dev/null 2>&1" - - return os.execute(cmd) -end - ---- Retrieve environment variables. If no variable is given then a table --- containing the whole environment is returned otherwise this function returns --- the corresponding string value for the given name or nil if no such variable --- exists. --- @class function --- @name getenv --- @param var Name of the environment variable to retrieve (optional) --- @return String containg the value of the specified variable --- @return Table containing all variables if no variable name is given -getenv = posix.getenv - ---- Determine the current hostname. --- @return String containing the system hostname -function hostname() - return io.lines("/proc/sys/kernel/hostname")() -end - ---- Returns the contents of a documented referred by an URL. --- @param url The URL to retrieve --- @param stream Return a stream instead of a buffer --- @return String containing the contents of given the URL -function httpget(url, stream) - local source = stream and io.open or luci.util.exec - return source("wget -qO- '"..url:gsub("'", "").."'") -end - ---- Returns the system load average values. --- @return String containing the average load value 1 minute ago --- @return String containing the average load value 5 minutes ago --- @return String containing the average load value 15 minutes ago --- @return String containing the active and total number of processes --- @return String containing the last used pid -function loadavg() - local loadavg = io.lines("/proc/loadavg")() - return loadavg:match("^(.-) (.-) (.-) (.-) (.-)$") -end - ---- Initiate a system reboot. --- @return Return value of os.execute() -function reboot() - return os.execute("reboot >/dev/null 2>&1") -end - ---- Returns the system type, cpu name and installed physical memory. --- @return String containing the system or platform identifier --- @return String containing hardware model information --- @return String containing the total memory amount in kB --- @return String containing the memory used for caching in kB --- @return String containing the memory used for buffering in kB --- @return String containing the free memory amount in kB --- @return Number containing free memory in percent --- @return Number containing buffer memory in percent --- @return Number containing cache memory in percent -function sysinfo() - local c1 = "cat /proc/cpuinfo|grep system\\ typ|cut -d: -f2 2>/dev/null" - local c2 = "uname -m 2>/dev/null" - local c3 = "cat /proc/cpuinfo|grep model\\ name|cut -d: -f2 2>/dev/null" - local c4 = "cat /proc/cpuinfo|grep cpu\\ model|cut -d: -f2 2>/dev/null" - local c5 = "cat /proc/meminfo|grep MemTotal|awk {' print $2 '} 2>/dev/null" - local c6 = "cat /proc/meminfo|grep ^Cached|awk {' print $2 '} 2>/dev/null" - local c7 = "cat /proc/meminfo|grep MemFree|awk {' print $2 '} 2>/dev/null" - local c8 = "cat /proc/meminfo|grep Buffers|awk {' print $2 '} 2>/dev/null" - - local system = luci.util.trim(luci.util.exec(c1)) - local model = "" - local memtotal = luci.util.trim(luci.util.exec(c5)) - local memcached = luci.util.trim(luci.util.exec(c6)) - local memfree = luci.util.trim(luci.util.exec(c7)) - local membuffers = luci.util.trim(luci.util.exec(c8)) - local perc_memfree = math.floor((memfree/memtotal)*100) - local perc_membuffers = math.floor((membuffers/memtotal)*100) - local perc_memcached = math.floor((memcached/memtotal)*100) - - if system == "" then - system = luci.util.trim(luci.util.exec(c2)) - model = luci.util.trim(luci.util.exec(c3)) - else - model = luci.util.trim(luci.util.exec(c4)) - end - - return system, model, memtotal, memcached, membuffers, memfree, perc_memfree, perc_membuffers, perc_memcached -end - ---- Retrieves the output of the "logread" command. --- @return String containing the current log buffer -function syslog() - return luci.util.exec("logread") -end - ---- Generates a random id with specified length. --- @param bytes Number of bytes for the unique id --- @return String containing hex encoded id -function uniqueid(bytes) - local fp = io.open("/dev/urandom") - local chunk = { fp:read(bytes):byte(1, bytes) } - fp:close() - - local hex = "" - - local pattern = "%02X" - for i, byte in ipairs(chunk) do - hex = hex .. pattern:format(byte) - end - - return hex -end - ---- Returns the current system uptime stats. --- @return String containing total uptime in seconds --- @return String containing idle time in seconds -function uptime() - local loadavg = io.lines("/proc/uptime")() - return loadavg:match("^(.-) (.-)$") -end - ---- LuCI system utilities / POSIX user group related functions. --- @class module --- @name luci.sys.group -group = {} - ---- Returns information about a POSIX user group. --- @param group Group ID or name of a system user group --- @return Table with information about the requested group -group.getgroup = posix.getgroup - - ---- LuCI system utilities / network related functions. --- @class module --- @name luci.sys.net -net = {} - ---- Returns the current arp-table entries as two-dimensional table. --- @return Table of table containing the current arp entries. --- The following fields are defined for arp entry objects: --- { "IP address", "HW address", "HW type", "Flags", "Mask", "Device" } -function net.arptable() - return _parse_delimited_table(io.lines("/proc/net/arp"), "%s%s+") -end - ---- Test whether an IP-Adress belongs to a certain net. --- @param ip IPv4 address to test --- @param ipnet IPv4 network address of the net range to compare against --- @param prefix Network prefix of the net range to compare against --- @return Boolean indicating wheather the ip is within the range -function net.belongs(ip, ipnet, prefix) - return (net.ip4bin(ip):sub(1, prefix) == net.ip4bin(ipnet):sub(1, prefix)) -end - ---- Determine the current default route. --- @return Table with the properties of the current default route. --- The following fields are defined: --- { "Mask", "RefCnt", "Iface", "Flags", "Window", "IRTT", --- "MTU", "Gateway", "Destination", "Metric", "Use" } -function net.defaultroute() - local routes = net.routes() - local route = nil - - for i, r in pairs(luci.sys.net.routes()) do - if r.Destination == "00000000" and (not route or route.Metric > r.Metric) then - route = r - end - end - - return route -end - ---- Determine the names of available network interfaces. --- @return Table containing all current interface names -function net.devices() - local devices = {} - for line in io.lines("/proc/net/dev") do - table.insert(devices, line:match(" *(.-):")) - end - return devices -end - --- Determine the MAC address belonging to the given IP address. --- @param ip IPv4 address --- @return String containing the MAC address or nil if it cannot be found -function net.ip4mac(ip) - local mac = nil - - for i, l in ipairs(net.arptable()) do - if l["IP address"] == ip then - mac = l["HW address"] - end - end - - return mac -end - ---- Calculate the prefix from a given netmask. --- @param mask IPv4 net mask --- @return Number containing the corresponding numerical prefix -function net.mask4prefix(mask) - local bin = net.ip4bin(mask) - - if not bin then - return nil - end - - return #luci.util.split(bin, "1")-1 -end - ---- Returns the current kernel routing table entries. --- @return Table of tables with properties of the corresponding routes. --- The following fields are defined for route entry tables: --- { "Mask", "RefCnt", "Iface", "Flags", "Window", "IRTT", --- "MTU", "Gateway", "Destination", "Metric", "Use" } -function net.routes() - return _parse_delimited_table(io.lines("/proc/net/route")) -end - ---- Convert hexadecimal 32 bit value to IPv4 address. --- @param hex String containing the hexadecimal value --- @param be Boolean indicating wheather the given value is big endian --- @return String containing the corresponding IP4 address -function net.hexip4(hex, be) - if #hex ~= 8 then - return nil - end - - be = be or luci.util.bigendian() - - local hexdec = luci.bits.Hex2Dec - - local ip = "" - if be then - ip = ip .. tostring(hexdec(hex:sub(1,2))) .. "." - ip = ip .. tostring(hexdec(hex:sub(3,4))) .. "." - ip = ip .. tostring(hexdec(hex:sub(5,6))) .. "." - ip = ip .. tostring(hexdec(hex:sub(7,8))) - else - ip = ip .. tostring(hexdec(hex:sub(7,8))) .. "." - ip = ip .. tostring(hexdec(hex:sub(5,6))) .. "." - ip = ip .. tostring(hexdec(hex:sub(3,4))) .. "." - ip = ip .. tostring(hexdec(hex:sub(1,2))) - end - - return ip -end - ---- Convert given IPv4 address to binary value. --- @param ip String containing a IPv4 address --- @return String containing corresponding binary value -function net.ip4bin(ip) - local parts = luci.util.split(ip, '.') - if #parts ~= 4 then - return nil - end - - local decbin = luci.bits.Dec2Bin - - local bin = "" - bin = bin .. decbin(parts[1], 8) - bin = bin .. decbin(parts[2], 8) - bin = bin .. decbin(parts[3], 8) - bin = bin .. decbin(parts[4], 8) - - return bin -end - ---- Tests whether the given host responds to ping probes. --- @param host String containing a hostname or IPv4 address --- @return Number containing 0 on success and >= 1 on error -function net.pingtest(host) - return os.execute("ping -c1 '"..host:gsub("'", '').."' >/dev/null 2>&1") -end - - ---- LuCI system utilities / process related functions. --- @class module --- @name luci.sys.process -process = {} - ---- Get the current process id. --- @return Number containing the current pid -process.info = posix.getpid - ---- Set the gid of a process identified by given pid. --- @param pid Number containing the process id --- @param gid Number containing the Unix group id --- @return Boolean indicating successful operation --- @return String containing the error message if failed --- @return Number containing the error code if failed -function process.setgroup(pid, gid) - return posix.setpid("g", pid, gid) -end - ---- Set the uid of a process identified by given pid. --- @param pid Number containing the process id --- @param uid Number containing the Unix user id --- @return Boolean indicating successful operation --- @return String containing the error message if failed --- @return Number containing the error code if failed -function process.setuser(pid, uid) - return posix.setpid("u", pid, uid) -end - - ---- LuCI system utilities / user related functions. --- @class module --- @name luci.sys.user -user = {} - ---- Retrieve user informations for given uid. --- @class function --- @name getuser --- @param uid Number containing the Unix user id --- @return Table containing the following fields: --- { "uid", "gid", "name", "passwd", "dir", "shell", "gecos" } -user.getuser = posix.getpasswd - ---- Test whether given string matches the password of a given system user. --- @param username String containing the Unix user name --- @param password String containing the password to compare --- @return Boolean indicating wheather the passwords are equal -function user.checkpasswd(username, password) - local account = user.getuser(username) - - -- FIXME: detect testing environment - if luci.fs.stat("/etc/shadow") and not luci.fs.access("/etc/shadow", "r") then - return true - elseif account then - if account.passwd == "!" then - return true - else - return (account.passwd == posix.crypt(password, account.passwd)) - end - end -end - ---- Change the password of given user. --- @param username String containing the Unix user name --- @param password String containing the password to compare --- @return Number containing 0 on success and >= 1 on error -function user.setpasswd(username, password) - if password then - password = password:gsub("'", "") - end - - if username then - username = username:gsub("'", "") - end - - local cmd = "(echo '"..password.."';sleep 1;echo '"..password.."')|" - cmd = cmd .. "passwd '"..username.."' >/dev/null 2>&1" - return os.execute(cmd) -end - - ---- LuCI system utilities / wifi related functions. --- @class module --- @name luci.sys.wifi -wifi = {} - ---- Get iwconfig output for all wireless devices. --- @return Table of tables containing the iwconfing output for each wifi device -function wifi.getiwconfig() - local cnt = luci.util.exec("/usr/sbin/iwconfig 2>/dev/null") - local iwc = {} - - for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do - local k = l:match("^(.-) ") - l = l:gsub("^(.-) +", "", 1) - if k then - iwc[k] = _parse_mixed_record(l) - end - end - - return iwc -end - ---- Get iwlist scan output from all wireless devices. --- @return Table of tables contaiing all scan results -function wifi.iwscan() - local cnt = luci.util.exec("iwlist scan 2>/dev/null") - local iws = {} - - for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do - local k = l:match("^(.-) ") - l = l:gsub("^[^\n]+", "", 1) - l = luci.util.trim(l) - if k then - iws[k] = {} - for j, c in pairs(luci.util.split(l, "\n Cell")) do - c = c:gsub("^(.-)- ", "", 1) - c = luci.util.split(c, "\n", 7) - c = table.concat(c, "\n", 1) - table.insert(iws[k], _parse_mixed_record(c)) - end - end - end - - return iws -end - - --- Internal functions - -function _parse_delimited_table(iter, delimiter) - delimiter = delimiter or "%s+" - - local data = {} - local trim = luci.util.trim - local split = luci.util.split - - local keys = split(trim(iter()), delimiter, nil, true) - for i, j in pairs(keys) do - keys[i] = trim(keys[i]) - end - - for line in iter do - local row = {} - line = trim(line) - if #line > 0 then - for i, j in pairs(split(line, delimiter, nil, true)) do - if keys[i] then - row[keys[i]] = j - end - end - end - table.insert(data, row) - end - - return data -end - -function _parse_mixed_record(cnt) - local data = {} - - for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n")) do - for j, f in pairs(luci.util.split(luci.util.trim(l), " ")) do - local k, x, v = f:match('([^%s][^:=]+) *([:=]*) *"*([^\n"]*)"*') - - if k then - if x == "" then - table.insert(data, k) - else - data[k] = v - end - end - end - end - - return data -end diff --git a/libs/core/luasrc/sys/iptparser.lua b/libs/core/luasrc/sys/iptparser.lua deleted file mode 100644 index 2e8085a55..000000000 --- a/libs/core/luasrc/sys/iptparser.lua +++ /dev/null @@ -1,244 +0,0 @@ ---[[ -LuCI - Iptables parser and query library - -Copyright 2008 Jo-Philipp Wich - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. - -$Id$ - -]]-- - -module("luci.sys.iptparser", package.seeall) -require("luci.util") - - -IptParser = luci.util.class() - ---[[ -IptParser.__init__( ... ) - -The class constructor, initializes the internal lookup table. -]]-- - -function IptParser.__init__( self, ... ) - self._rules = { } - self._chain = nil - self:_parse_rules() -end - - ---[[ -IptParser.find( args ) - -Find all firewall rules that match the given criteria. Expects a table with search criteria as only argument. -If args is nil or an empty table then all rules will be returned. - -The following keys in the args table are recognized: - - - table Match rules that are located within the given table - - chain Match rules that are located within the given chain - - target Match rules with the given target - - protocol Match rules that match the given protocol, rules with protocol "all" are always matched - - source Match rules with the given source, rules with source "0.0.0.0/0" are always matched - - destination Match rules with the given destination, rules with destination "0.0.0.0/0" are always matched - - inputif Match rules with the given input interface, rules with input interface "*" (=all) are always matched - - outputif Match rules with the given output interface, rules with output interface "*" (=all) are always matched - - flags Match rules that match the given flags, current supported values are "-f" (--fragment) and "!f" (! --fragment) - - options Match rules containing all given options - -The return value is a list of tables representing the matched rules. -Each rule table contains the following fields: - - - index The index number of the rule - - table The table where the rule is located, can be one of "filter", "nat" or "mangle" - - chain The chain where the rule is located, e.g. "INPUT" or "postrouting_wan" - - target The rule target, e.g. "REJECT" or "DROP" - - protocol The matching protocols, e.g. "all" or "tcp" - - flags Special rule options ("--", "-f" or "!f") - - inputif Input interface of the rule, e.g. "eth0.0" or "*" for all interfaces - - outputif Output interface of the rule, e.g. "eth0.0" or "*" for all interfaces - - source The source ip range, e.g. "0.0.0.0/0" - - destination The destination ip range, e.g. "0.0.0.0/0" - - options A list of specific options of the rule, e.g. { "reject-with", "tcp-reset" } - - packets The number of packets matched by the rule - - bytes The number of total bytes matched by the rule - -Example: - -ip = luci.sys.iptparser.IptParser() -result = ip.find( { - target="REJECT", - protocol="tcp", - options={ "reject-with", "tcp-reset" } -} ) - -This will match all rules with target "-j REJECT", protocol "-p tcp" (or "-p all") and the option "--reject-with tcp-reset". - -]]-- - -function IptParser.find( self, args ) - - local args = args or { } - local rv = { } - - for i, rule in ipairs(self._rules) do - local match = true - - -- match table - if not ( not args.table or args.table == rule.table ) then - match = false - end - - -- match chain - if not ( match == true and ( not args.chain or args.chain == rule.chain ) ) then - match = false - end - - -- match target - if not ( match == true and ( not args.target or args.target == rule.target ) ) then - match = false - end - - -- match protocol - if not ( match == true and ( not args.protocol or rule.protocol == "all" or args.protocol == rule.protocol ) ) then - match = false - end - - -- match source (XXX: implement ipcalc stuff so that 192.168.1.0/24 matches 0.0.0.0/0 etc.) - if not ( match == true and ( not args.source or rule.source == "0.0.0.0/0" or rule.source == args.source ) ) then - match = false - end - - -- match destination (XXX: implement ipcalc stuff so that 192.168.1.0/24 matches 0.0.0.0/0 etc.) - if not ( match == true and ( not args.destination or rule.destination == "0.0.0.0/0" or rule.destination == args.destination ) ) then - match = false - end - - -- match input interface - if not ( match == true and ( not args.inputif or rule.inputif == "*" or args.inputif == rule.inputif ) ) then - match = false - end - - -- match output interface - if not ( match == true and ( not args.outputif or rule.outputif == "*" or args.outputif == rule.outputif ) ) then - match = false - end - - -- match flags (the "opt" column) - if not ( match == true and ( not args.flags or rule.flags == args.flags ) ) then - match = false - end - - -- match specific options - if not ( match == true and ( not args.options or self:_match_options( rule.options, args.options ) ) ) then - match = false - end - - - -- insert match - if match == true then - table.insert( rv, rule ) - end - end - - return rv -end - - ---[[ -IptParser.resync() - -Rebuild the internal lookup table, for example when rules have changed through external commands. -]]-- - -function IptParser.resync( self ) - self._rules = { } - self._chain = nil - self:_parse_rules() -end - - ---[[ -IptParser._parse_rules() - -[internal] Parse iptables output from all tables. -]]-- - -function IptParser._parse_rules( self ) - - for i, tbl in ipairs({ "filter", "nat", "mangle" }) do - - for i, rule in ipairs(luci.util.execl("iptables -t " .. tbl .. " --line-numbers -nxvL")) do - - if rule:find( "Chain " ) == 1 then - - self._chain = rule:gsub("Chain ([^%s]*) .*", "%1") - - else - if rule:find("%d") == 1 then - - local rule_parts = luci.util.split( rule, "%s+", nil, true ) - local rule_details = { } - - rule_details["table"] = tbl - rule_details["chain"] = self._chain - rule_details["index"] = tonumber(rule_parts[1]) - rule_details["packets"] = tonumber(rule_parts[2]) - rule_details["bytes"] = tonumber(rule_parts[3]) - rule_details["target"] = rule_parts[4] - rule_details["protocol"] = rule_parts[5] - rule_details["flags"] = rule_parts[6] - rule_details["inputif"] = rule_parts[7] - rule_details["outputif"] = rule_parts[8] - rule_details["source"] = rule_parts[9] - rule_details["destination"] = rule_parts[10] - rule_details["options"] = { } - - for i = 11, #rule_parts - 1 do - rule_details["options"][i-10] = rule_parts[i] - end - - table.insert( self._rules, rule_details ) - end - end - end - end - - self._chain = nil -end - - ---[[ -IptParser._match_options( optlist1, optlist2 ) - -[internal] Return true if optlist1 contains all elements of optlist2. Return false in all other cases. -]]-- - -function IptParser._match_options( self, o1, o2 ) - - -- construct a hashtable of first options list to speed up lookups - local oh = { } - for i, opt in ipairs( o1 ) do oh[opt] = true end - - -- iterate over second options list - -- each string in o2 must be also present in o1 - -- if o2 contains a string which is not found in o1 then return false - for i, opt in ipairs( o2 ) do - if not oh[opt] then - return false - end - end - - return true -end diff --git a/libs/sys/luasrc/sys.lua b/libs/sys/luasrc/sys.lua new file mode 100644 index 000000000..14a2f294f --- /dev/null +++ b/libs/sys/luasrc/sys.lua @@ -0,0 +1,493 @@ +--[[ +LuCI - System library + +Description: +Utilities for interaction with the Linux system + +FileId: +$Id$ + +License: +Copyright 2008 Steven Barth + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +]]-- + +--- LuCI Linux and POSIX system utilities. +module("luci.sys", package.seeall) +require("posix") +require("luci.bits") +require("luci.util") +require("luci.fs") + +--- Invoke the luci-flash executable to write an image to the flash memory. +-- @param kpattern Pattern of files to keep over flash process +-- @return Return value of os.execute() +function flash(image, kpattern) + local cmd = "luci-flash " + if kpattern then + cmd = cmd .. "-k '" .. kpattern:gsub("'", "") .. "' " + end + cmd = cmd .. "'" .. image:gsub("'", "") .. "' >/dev/null 2>&1" + + return os.execute(cmd) +end + +--- Retrieve environment variables. If no variable is given then a table +-- containing the whole environment is returned otherwise this function returns +-- the corresponding string value for the given name or nil if no such variable +-- exists. +-- @class function +-- @name getenv +-- @param var Name of the environment variable to retrieve (optional) +-- @return String containg the value of the specified variable +-- @return Table containing all variables if no variable name is given +getenv = posix.getenv + +--- Determine the current hostname. +-- @return String containing the system hostname +function hostname() + return io.lines("/proc/sys/kernel/hostname")() +end + +--- Returns the contents of a documented referred by an URL. +-- @param url The URL to retrieve +-- @param stream Return a stream instead of a buffer +-- @return String containing the contents of given the URL +function httpget(url, stream) + local source = stream and io.open or luci.util.exec + return source("wget -qO- '"..url:gsub("'", "").."'") +end + +--- Returns the system load average values. +-- @return String containing the average load value 1 minute ago +-- @return String containing the average load value 5 minutes ago +-- @return String containing the average load value 15 minutes ago +-- @return String containing the active and total number of processes +-- @return String containing the last used pid +function loadavg() + local loadavg = io.lines("/proc/loadavg")() + return loadavg:match("^(.-) (.-) (.-) (.-) (.-)$") +end + +--- Initiate a system reboot. +-- @return Return value of os.execute() +function reboot() + return os.execute("reboot >/dev/null 2>&1") +end + +--- Returns the system type, cpu name and installed physical memory. +-- @return String containing the system or platform identifier +-- @return String containing hardware model information +-- @return String containing the total memory amount in kB +-- @return String containing the memory used for caching in kB +-- @return String containing the memory used for buffering in kB +-- @return String containing the free memory amount in kB +-- @return Number containing free memory in percent +-- @return Number containing buffer memory in percent +-- @return Number containing cache memory in percent +function sysinfo() + local c1 = "cat /proc/cpuinfo|grep system\\ typ|cut -d: -f2 2>/dev/null" + local c2 = "uname -m 2>/dev/null" + local c3 = "cat /proc/cpuinfo|grep model\\ name|cut -d: -f2 2>/dev/null" + local c4 = "cat /proc/cpuinfo|grep cpu\\ model|cut -d: -f2 2>/dev/null" + local c5 = "cat /proc/meminfo|grep MemTotal|awk {' print $2 '} 2>/dev/null" + local c6 = "cat /proc/meminfo|grep ^Cached|awk {' print $2 '} 2>/dev/null" + local c7 = "cat /proc/meminfo|grep MemFree|awk {' print $2 '} 2>/dev/null" + local c8 = "cat /proc/meminfo|grep Buffers|awk {' print $2 '} 2>/dev/null" + + local system = luci.util.trim(luci.util.exec(c1)) + local model = "" + local memtotal = luci.util.trim(luci.util.exec(c5)) + local memcached = luci.util.trim(luci.util.exec(c6)) + local memfree = luci.util.trim(luci.util.exec(c7)) + local membuffers = luci.util.trim(luci.util.exec(c8)) + local perc_memfree = math.floor((memfree/memtotal)*100) + local perc_membuffers = math.floor((membuffers/memtotal)*100) + local perc_memcached = math.floor((memcached/memtotal)*100) + + if system == "" then + system = luci.util.trim(luci.util.exec(c2)) + model = luci.util.trim(luci.util.exec(c3)) + else + model = luci.util.trim(luci.util.exec(c4)) + end + + return system, model, memtotal, memcached, membuffers, memfree, perc_memfree, perc_membuffers, perc_memcached +end + +--- Retrieves the output of the "logread" command. +-- @return String containing the current log buffer +function syslog() + return luci.util.exec("logread") +end + +--- Generates a random id with specified length. +-- @param bytes Number of bytes for the unique id +-- @return String containing hex encoded id +function uniqueid(bytes) + local fp = io.open("/dev/urandom") + local chunk = { fp:read(bytes):byte(1, bytes) } + fp:close() + + local hex = "" + + local pattern = "%02X" + for i, byte in ipairs(chunk) do + hex = hex .. pattern:format(byte) + end + + return hex +end + +--- Returns the current system uptime stats. +-- @return String containing total uptime in seconds +-- @return String containing idle time in seconds +function uptime() + local loadavg = io.lines("/proc/uptime")() + return loadavg:match("^(.-) (.-)$") +end + +--- LuCI system utilities / POSIX user group related functions. +-- @class module +-- @name luci.sys.group +group = {} + +--- Returns information about a POSIX user group. +-- @param group Group ID or name of a system user group +-- @return Table with information about the requested group +group.getgroup = posix.getgroup + + +--- LuCI system utilities / network related functions. +-- @class module +-- @name luci.sys.net +net = {} + +--- Returns the current arp-table entries as two-dimensional table. +-- @return Table of table containing the current arp entries. +-- The following fields are defined for arp entry objects: +-- { "IP address", "HW address", "HW type", "Flags", "Mask", "Device" } +function net.arptable() + return _parse_delimited_table(io.lines("/proc/net/arp"), "%s%s+") +end + +--- Test whether an IP-Adress belongs to a certain net. +-- @param ip IPv4 address to test +-- @param ipnet IPv4 network address of the net range to compare against +-- @param prefix Network prefix of the net range to compare against +-- @return Boolean indicating wheather the ip is within the range +function net.belongs(ip, ipnet, prefix) + return (net.ip4bin(ip):sub(1, prefix) == net.ip4bin(ipnet):sub(1, prefix)) +end + +--- Determine the current default route. +-- @return Table with the properties of the current default route. +-- The following fields are defined: +-- { "Mask", "RefCnt", "Iface", "Flags", "Window", "IRTT", +-- "MTU", "Gateway", "Destination", "Metric", "Use" } +function net.defaultroute() + local routes = net.routes() + local route = nil + + for i, r in pairs(luci.sys.net.routes()) do + if r.Destination == "00000000" and (not route or route.Metric > r.Metric) then + route = r + end + end + + return route +end + +--- Determine the names of available network interfaces. +-- @return Table containing all current interface names +function net.devices() + local devices = {} + for line in io.lines("/proc/net/dev") do + table.insert(devices, line:match(" *(.-):")) + end + return devices +end + +-- Determine the MAC address belonging to the given IP address. +-- @param ip IPv4 address +-- @return String containing the MAC address or nil if it cannot be found +function net.ip4mac(ip) + local mac = nil + + for i, l in ipairs(net.arptable()) do + if l["IP address"] == ip then + mac = l["HW address"] + end + end + + return mac +end + +--- Calculate the prefix from a given netmask. +-- @param mask IPv4 net mask +-- @return Number containing the corresponding numerical prefix +function net.mask4prefix(mask) + local bin = net.ip4bin(mask) + + if not bin then + return nil + end + + return #luci.util.split(bin, "1")-1 +end + +--- Returns the current kernel routing table entries. +-- @return Table of tables with properties of the corresponding routes. +-- The following fields are defined for route entry tables: +-- { "Mask", "RefCnt", "Iface", "Flags", "Window", "IRTT", +-- "MTU", "Gateway", "Destination", "Metric", "Use" } +function net.routes() + return _parse_delimited_table(io.lines("/proc/net/route")) +end + +--- Convert hexadecimal 32 bit value to IPv4 address. +-- @param hex String containing the hexadecimal value +-- @param be Boolean indicating wheather the given value is big endian +-- @return String containing the corresponding IP4 address +function net.hexip4(hex, be) + if #hex ~= 8 then + return nil + end + + be = be or luci.util.bigendian() + + local hexdec = luci.bits.Hex2Dec + + local ip = "" + if be then + ip = ip .. tostring(hexdec(hex:sub(1,2))) .. "." + ip = ip .. tostring(hexdec(hex:sub(3,4))) .. "." + ip = ip .. tostring(hexdec(hex:sub(5,6))) .. "." + ip = ip .. tostring(hexdec(hex:sub(7,8))) + else + ip = ip .. tostring(hexdec(hex:sub(7,8))) .. "." + ip = ip .. tostring(hexdec(hex:sub(5,6))) .. "." + ip = ip .. tostring(hexdec(hex:sub(3,4))) .. "." + ip = ip .. tostring(hexdec(hex:sub(1,2))) + end + + return ip +end + +--- Convert given IPv4 address to binary value. +-- @param ip String containing a IPv4 address +-- @return String containing corresponding binary value +function net.ip4bin(ip) + local parts = luci.util.split(ip, '.') + if #parts ~= 4 then + return nil + end + + local decbin = luci.bits.Dec2Bin + + local bin = "" + bin = bin .. decbin(parts[1], 8) + bin = bin .. decbin(parts[2], 8) + bin = bin .. decbin(parts[3], 8) + bin = bin .. decbin(parts[4], 8) + + return bin +end + +--- Tests whether the given host responds to ping probes. +-- @param host String containing a hostname or IPv4 address +-- @return Number containing 0 on success and >= 1 on error +function net.pingtest(host) + return os.execute("ping -c1 '"..host:gsub("'", '').."' >/dev/null 2>&1") +end + + +--- LuCI system utilities / process related functions. +-- @class module +-- @name luci.sys.process +process = {} + +--- Get the current process id. +-- @return Number containing the current pid +process.info = posix.getpid + +--- Set the gid of a process identified by given pid. +-- @param pid Number containing the process id +-- @param gid Number containing the Unix group id +-- @return Boolean indicating successful operation +-- @return String containing the error message if failed +-- @return Number containing the error code if failed +function process.setgroup(pid, gid) + return posix.setpid("g", pid, gid) +end + +--- Set the uid of a process identified by given pid. +-- @param pid Number containing the process id +-- @param uid Number containing the Unix user id +-- @return Boolean indicating successful operation +-- @return String containing the error message if failed +-- @return Number containing the error code if failed +function process.setuser(pid, uid) + return posix.setpid("u", pid, uid) +end + + +--- LuCI system utilities / user related functions. +-- @class module +-- @name luci.sys.user +user = {} + +--- Retrieve user informations for given uid. +-- @class function +-- @name getuser +-- @param uid Number containing the Unix user id +-- @return Table containing the following fields: +-- { "uid", "gid", "name", "passwd", "dir", "shell", "gecos" } +user.getuser = posix.getpasswd + +--- Test whether given string matches the password of a given system user. +-- @param username String containing the Unix user name +-- @param password String containing the password to compare +-- @return Boolean indicating wheather the passwords are equal +function user.checkpasswd(username, password) + local account = user.getuser(username) + + -- FIXME: detect testing environment + if luci.fs.stat("/etc/shadow") and not luci.fs.access("/etc/shadow", "r") then + return true + elseif account then + if account.passwd == "!" then + return true + else + return (account.passwd == posix.crypt(password, account.passwd)) + end + end +end + +--- Change the password of given user. +-- @param username String containing the Unix user name +-- @param password String containing the password to compare +-- @return Number containing 0 on success and >= 1 on error +function user.setpasswd(username, password) + if password then + password = password:gsub("'", "") + end + + if username then + username = username:gsub("'", "") + end + + local cmd = "(echo '"..password.."';sleep 1;echo '"..password.."')|" + cmd = cmd .. "passwd '"..username.."' >/dev/null 2>&1" + return os.execute(cmd) +end + + +--- LuCI system utilities / wifi related functions. +-- @class module +-- @name luci.sys.wifi +wifi = {} + +--- Get iwconfig output for all wireless devices. +-- @return Table of tables containing the iwconfing output for each wifi device +function wifi.getiwconfig() + local cnt = luci.util.exec("/usr/sbin/iwconfig 2>/dev/null") + local iwc = {} + + for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do + local k = l:match("^(.-) ") + l = l:gsub("^(.-) +", "", 1) + if k then + iwc[k] = _parse_mixed_record(l) + end + end + + return iwc +end + +--- Get iwlist scan output from all wireless devices. +-- @return Table of tables contaiing all scan results +function wifi.iwscan() + local cnt = luci.util.exec("iwlist scan 2>/dev/null") + local iws = {} + + for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n\n")) do + local k = l:match("^(.-) ") + l = l:gsub("^[^\n]+", "", 1) + l = luci.util.trim(l) + if k then + iws[k] = {} + for j, c in pairs(luci.util.split(l, "\n Cell")) do + c = c:gsub("^(.-)- ", "", 1) + c = luci.util.split(c, "\n", 7) + c = table.concat(c, "\n", 1) + table.insert(iws[k], _parse_mixed_record(c)) + end + end + end + + return iws +end + + +-- Internal functions + +function _parse_delimited_table(iter, delimiter) + delimiter = delimiter or "%s+" + + local data = {} + local trim = luci.util.trim + local split = luci.util.split + + local keys = split(trim(iter()), delimiter, nil, true) + for i, j in pairs(keys) do + keys[i] = trim(keys[i]) + end + + for line in iter do + local row = {} + line = trim(line) + if #line > 0 then + for i, j in pairs(split(line, delimiter, nil, true)) do + if keys[i] then + row[keys[i]] = j + end + end + end + table.insert(data, row) + end + + return data +end + +function _parse_mixed_record(cnt) + local data = {} + + for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n")) do + for j, f in pairs(luci.util.split(luci.util.trim(l), " ")) do + local k, x, v = f:match('([^%s][^:=]+) *([:=]*) *"*([^\n"]*)"*') + + if k then + if x == "" then + table.insert(data, k) + else + data[k] = v + end + end + end + end + + return data +end diff --git a/libs/sys/luasrc/sys/iptparser.lua b/libs/sys/luasrc/sys/iptparser.lua new file mode 100644 index 000000000..2e8085a55 --- /dev/null +++ b/libs/sys/luasrc/sys/iptparser.lua @@ -0,0 +1,244 @@ +--[[ +LuCI - Iptables parser and query library + +Copyright 2008 Jo-Philipp Wich + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +$Id$ + +]]-- + +module("luci.sys.iptparser", package.seeall) +require("luci.util") + + +IptParser = luci.util.class() + +--[[ +IptParser.__init__( ... ) + +The class constructor, initializes the internal lookup table. +]]-- + +function IptParser.__init__( self, ... ) + self._rules = { } + self._chain = nil + self:_parse_rules() +end + + +--[[ +IptParser.find( args ) + +Find all firewall rules that match the given criteria. Expects a table with search criteria as only argument. +If args is nil or an empty table then all rules will be returned. + +The following keys in the args table are recognized: + + - table Match rules that are located within the given table + - chain Match rules that are located within the given chain + - target Match rules with the given target + - protocol Match rules that match the given protocol, rules with protocol "all" are always matched + - source Match rules with the given source, rules with source "0.0.0.0/0" are always matched + - destination Match rules with the given destination, rules with destination "0.0.0.0/0" are always matched + - inputif Match rules with the given input interface, rules with input interface "*" (=all) are always matched + - outputif Match rules with the given output interface, rules with output interface "*" (=all) are always matched + - flags Match rules that match the given flags, current supported values are "-f" (--fragment) and "!f" (! --fragment) + - options Match rules containing all given options + +The return value is a list of tables representing the matched rules. +Each rule table contains the following fields: + + - index The index number of the rule + - table The table where the rule is located, can be one of "filter", "nat" or "mangle" + - chain The chain where the rule is located, e.g. "INPUT" or "postrouting_wan" + - target The rule target, e.g. "REJECT" or "DROP" + - protocol The matching protocols, e.g. "all" or "tcp" + - flags Special rule options ("--", "-f" or "!f") + - inputif Input interface of the rule, e.g. "eth0.0" or "*" for all interfaces + - outputif Output interface of the rule, e.g. "eth0.0" or "*" for all interfaces + - source The source ip range, e.g. "0.0.0.0/0" + - destination The destination ip range, e.g. "0.0.0.0/0" + - options A list of specific options of the rule, e.g. { "reject-with", "tcp-reset" } + - packets The number of packets matched by the rule + - bytes The number of total bytes matched by the rule + +Example: + +ip = luci.sys.iptparser.IptParser() +result = ip.find( { + target="REJECT", + protocol="tcp", + options={ "reject-with", "tcp-reset" } +} ) + +This will match all rules with target "-j REJECT", protocol "-p tcp" (or "-p all") and the option "--reject-with tcp-reset". + +]]-- + +function IptParser.find( self, args ) + + local args = args or { } + local rv = { } + + for i, rule in ipairs(self._rules) do + local match = true + + -- match table + if not ( not args.table or args.table == rule.table ) then + match = false + end + + -- match chain + if not ( match == true and ( not args.chain or args.chain == rule.chain ) ) then + match = false + end + + -- match target + if not ( match == true and ( not args.target or args.target == rule.target ) ) then + match = false + end + + -- match protocol + if not ( match == true and ( not args.protocol or rule.protocol == "all" or args.protocol == rule.protocol ) ) then + match = false + end + + -- match source (XXX: implement ipcalc stuff so that 192.168.1.0/24 matches 0.0.0.0/0 etc.) + if not ( match == true and ( not args.source or rule.source == "0.0.0.0/0" or rule.source == args.source ) ) then + match = false + end + + -- match destination (XXX: implement ipcalc stuff so that 192.168.1.0/24 matches 0.0.0.0/0 etc.) + if not ( match == true and ( not args.destination or rule.destination == "0.0.0.0/0" or rule.destination == args.destination ) ) then + match = false + end + + -- match input interface + if not ( match == true and ( not args.inputif or rule.inputif == "*" or args.inputif == rule.inputif ) ) then + match = false + end + + -- match output interface + if not ( match == true and ( not args.outputif or rule.outputif == "*" or args.outputif == rule.outputif ) ) then + match = false + end + + -- match flags (the "opt" column) + if not ( match == true and ( not args.flags or rule.flags == args.flags ) ) then + match = false + end + + -- match specific options + if not ( match == true and ( not args.options or self:_match_options( rule.options, args.options ) ) ) then + match = false + end + + + -- insert match + if match == true then + table.insert( rv, rule ) + end + end + + return rv +end + + +--[[ +IptParser.resync() + +Rebuild the internal lookup table, for example when rules have changed through external commands. +]]-- + +function IptParser.resync( self ) + self._rules = { } + self._chain = nil + self:_parse_rules() +end + + +--[[ +IptParser._parse_rules() + +[internal] Parse iptables output from all tables. +]]-- + +function IptParser._parse_rules( self ) + + for i, tbl in ipairs({ "filter", "nat", "mangle" }) do + + for i, rule in ipairs(luci.util.execl("iptables -t " .. tbl .. " --line-numbers -nxvL")) do + + if rule:find( "Chain " ) == 1 then + + self._chain = rule:gsub("Chain ([^%s]*) .*", "%1") + + else + if rule:find("%d") == 1 then + + local rule_parts = luci.util.split( rule, "%s+", nil, true ) + local rule_details = { } + + rule_details["table"] = tbl + rule_details["chain"] = self._chain + rule_details["index"] = tonumber(rule_parts[1]) + rule_details["packets"] = tonumber(rule_parts[2]) + rule_details["bytes"] = tonumber(rule_parts[3]) + rule_details["target"] = rule_parts[4] + rule_details["protocol"] = rule_parts[5] + rule_details["flags"] = rule_parts[6] + rule_details["inputif"] = rule_parts[7] + rule_details["outputif"] = rule_parts[8] + rule_details["source"] = rule_parts[9] + rule_details["destination"] = rule_parts[10] + rule_details["options"] = { } + + for i = 11, #rule_parts - 1 do + rule_details["options"][i-10] = rule_parts[i] + end + + table.insert( self._rules, rule_details ) + end + end + end + end + + self._chain = nil +end + + +--[[ +IptParser._match_options( optlist1, optlist2 ) + +[internal] Return true if optlist1 contains all elements of optlist2. Return false in all other cases. +]]-- + +function IptParser._match_options( self, o1, o2 ) + + -- construct a hashtable of first options list to speed up lookups + local oh = { } + for i, opt in ipairs( o1 ) do oh[opt] = true end + + -- iterate over second options list + -- each string in o2 must be also present in o1 + -- if o2 contains a string which is not found in o1 then return false + for i, opt in ipairs( o2 ) do + if not oh[opt] then + return false + end + end + + return true +end