From 6509e007e29f438d74b01eb6e23a09f02775dd6c Mon Sep 17 00:00:00 2001 From: Daniel Dickinson Date: Tue, 1 Dec 2015 23:02:38 -0500 Subject: luci-base: Make default for FileUpload 'safe' Some files and pointers to files are not safe to remove without a replacement file and config pointing to the file. For instance for uhttpd application in the works, removing the certificate or key config or files without having the replacements in places renders the WeUI inaccessible. The only other place where FileUpload is currently used is for wifi certificates for which the 'safe' handling is also preferred. Therefore make the default for the FileUpload widget the safe handling and add a property self.unsafeupload that allows for the old unsafe handling should it prove useful in some case. Also allow to specify a file already on router instead of uploading a file. Signed-off By: Daniel Dickinson --- modules/luci-base/luasrc/cbi.lua | 58 +++++++++++++++++++++------- modules/luci-base/luasrc/view/cbi/upload.htm | 16 ++++++-- 2 files changed, 57 insertions(+), 17 deletions(-) diff --git a/modules/luci-base/luasrc/cbi.lua b/modules/luci-base/luasrc/cbi.lua index b5b2c8d8f2..9b88e9fd9f 100644 --- a/modules/luci-base/luasrc/cbi.lua +++ b/modules/luci-base/luasrc/cbi.lua @@ -1811,6 +1811,7 @@ function Button.__init__(self, ...) self.template = "cbi/button" self.inputstyle = nil self.rmempty = true + self.unsafeupload = false end @@ -1827,9 +1828,15 @@ function FileUpload.__init__(self, ...) end function FileUpload.formcreated(self, section) - return AbstractValue.formcreated(self, section) or - self.map:formvalue("cbi.rlf."..section.."."..self.option) or - self.map:formvalue("cbi.rlf."..section.."."..self.option..".x") + if self.unsafeupload then + return AbstractValue.formcreated(self, section) or + self.map:formvalue("cbi.rlf."..section.."."..self.option) or + self.map:formvalue("cbi.rlf."..section.."."..self.option..".x") or + self.map:formvalue("cbid."..self.map.config.."."..section.."."..self.option..".textbox") + else + return AbstractValue.formcreated(self, section) or + self.map:formvalue("cbid."..self.map.config.."."..section.."."..self.option..".textbox") + end end function FileUpload.cfgvalue(self, section) @@ -1840,27 +1847,50 @@ function FileUpload.cfgvalue(self, section) return nil end +-- If we have a new value, use it +-- otherwise use old value +-- deletion should be managed by a separate button object +-- unless self.unsafeupload is set in which case if the user +-- choose to remove the old file we do so. +-- Also, allow to specify (via textbox) a file already on router function FileUpload.formvalue(self, section) local val = AbstractValue.formvalue(self, section) if val then - if not self.map:formvalue("cbi.rlf."..section.."."..self.option) and - not self.map:formvalue("cbi.rlf."..section.."."..self.option..".x") - then + if self.unsafeupload then + if not self.map:formvalue("cbi.rlf."..section.."."..self.option) and + not self.map:formvalue("cbi.rlf."..section.."."..self.option..".x") + then + return val + end + fs.unlink(val) + self.value = nil + return nil + elseif val ~= "" then return val - end - fs.unlink(val) - self.value = nil + end end - return nil + val = luci.http.formvalue("cbid."..self.map.config.."."..section.."."..self.option..".textbox") + if val == "" then + val = nil + end + if not self.unsafeupload then + if not val then + val = self.map:formvalue("cbi.rlf."..section.."."..self.option) + end + end + return val end function FileUpload.remove(self, section) - local val = AbstractValue.formvalue(self, section) - if val and fs.access(val) then fs.unlink(val) end - return AbstractValue.remove(self, section) + if self.unsafeupload then + local val = AbstractValue.formvalue(self, section) + if val and fs.access(val) then fs.unlink(val) end + return AbstractValue.remove(self, section) + else + return nil + end end - FileBrowser = class(AbstractValue) function FileBrowser.__init__(self, ...) diff --git a/modules/luci-base/luasrc/view/cbi/upload.htm b/modules/luci-base/luasrc/view/cbi/upload.htm index 7770934111..157f3b36fa 100644 --- a/modules/luci-base/luasrc/view/cbi/upload.htm +++ b/modules/luci-base/luasrc/view/cbi/upload.htm @@ -6,9 +6,19 @@ <%+cbi/valueheader%> <% if s then %> <%:Uploaded File%> (<%=t.byte_format(s.size)%>) - /> - " alt="<%:Replace entry%>" title="<%:Replace entry%>" src="<%=resource%>/cbi/reload.gif" /> - <% else %> + <% if self.unsafeupload then %> + /> + " alt="<%:Replace entry%>" title="<%:Replace entry%>" src="<%=resource%>/cbi/reload.gif" /> + <% end %> + <% end %> + + <% if not self.unsafeupload then %> + /> + <% end %> + + <% if (not s) or (s and not self.unsafeupload) then %> /> <% end %> + /> <%+cbi/valuefooter%> -- cgit v1.2.3 From d83642c20519692c5d3883e23d01bb84a48dd85a Mon Sep 17 00:00:00 2001 From: Daniel Dickinson Date: Wed, 2 Dec 2015 00:25:30 -0500 Subject: applications: uhttpd: Add a uHTTPd configuration app uHTTPd has a lot of useful options that ought to be configurable without have to SSH in, so expose those options in a UI page. --- applications/luci-app-uhttpd/Makefile | 28 +++ .../luasrc/controller/uhttpd/uhttpd.lua | 17 ++ .../luasrc/model/cbi/uhttpd/uhttpd.lua | 227 +++++++++++++++++++++ 3 files changed, 272 insertions(+) create mode 100644 applications/luci-app-uhttpd/Makefile create mode 100644 applications/luci-app-uhttpd/luasrc/controller/uhttpd/uhttpd.lua create mode 100644 applications/luci-app-uhttpd/luasrc/model/cbi/uhttpd/uhttpd.lua diff --git a/applications/luci-app-uhttpd/Makefile b/applications/luci-app-uhttpd/Makefile new file mode 100644 index 0000000000..9a2cf462e5 --- /dev/null +++ b/applications/luci-app-uhttpd/Makefile @@ -0,0 +1,28 @@ +# +# Copyright (C) 2015 OpenWrt.org +# +# This is free software, licensed under the GNU General Public License v2. +# See /LICENSE for more information. +# + +include $(TOPDIR)/rules.mk + +LUCI_TITLE:=uHTTPd Webserver Configuration +LUCI_DEPENDS:=+uhttpd +LUCI_PKGARCH:=all + +PKG_NAME:=luci-app-uhttpd +PKG_VERSION:=1.0.0 +PKG_RELEASE:=1 +PKG_LICENSE:=Apache-2.0 +PKG_MAINTAINER:=Daniel Dickinson + +PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME) + +include $(INCLUDE_DIR)/package.mk + +LUA_TARGET:=source + +include ../../luci.mk + +# call BuildPackage - OpenWrt buildroot signature diff --git a/applications/luci-app-uhttpd/luasrc/controller/uhttpd/uhttpd.lua b/applications/luci-app-uhttpd/luasrc/controller/uhttpd/uhttpd.lua new file mode 100644 index 0000000000..2e80dd63b4 --- /dev/null +++ b/applications/luci-app-uhttpd/luasrc/controller/uhttpd/uhttpd.lua @@ -0,0 +1,17 @@ +-- Copyright 2015 Daniel Dickinson +-- Licensed to the public under the Apache License 2.0. + +module("luci.controller.uhttpd.uhttpd", package.seeall) + +function index() + if not nixio.fs.access("/etc/config/uhttpd") then + return + end + + local page + + page = entry({"admin", "services", "uhttpd"}, cbi("uhttpd/uhttpd"), _("uHTTPd")) + page.leaf = true + +end + diff --git a/applications/luci-app-uhttpd/luasrc/model/cbi/uhttpd/uhttpd.lua b/applications/luci-app-uhttpd/luasrc/model/cbi/uhttpd/uhttpd.lua new file mode 100644 index 0000000000..03821ad9b5 --- /dev/null +++ b/applications/luci-app-uhttpd/luasrc/model/cbi/uhttpd/uhttpd.lua @@ -0,0 +1,227 @@ +-- Copyright 2015 Daniel Dickinson +-- Licensed to the public under the Apache License 2.0. + +local fs = require("nixio.fs") + +local m = Map("uhttpd", translate("uHTTPd"), + translate("A lightweight single-threaded HTTP(S) server")) + +local ucs = m:section(TypedSection, "uhttpd", "") +ucs.addremove = true +ucs.anonymous = false + +local lhttp = nil +local lhttps = nil +local cert_file = nil +local key_file = nil + +ucs:tab("general", translate("General Settings")) +ucs:tab("server", translate("Full Web Server Settings"), translate("For settings primarily geared to serving more than the web UI")) +ucs:tab("advanced", translate("Advanced Settings"), translate("Settings which are either rarely needed or which affect serving the WebUI")) + +lhttp = ucs:taboption("general", DynamicList, "listen_http", translate("HTTP listeners (address:port)"), translate("Bind to specific interface:port (by specifying interface address")) +lhttp.datatype = "list(ipaddrport(1))" + +function lhttp.validate(self, value, section) + local have_https_listener = false + local have_http_listener = false + if lhttp and lhttp:formvalue(section) and (#(lhttp:formvalue(section)) > 0) then + for k, v in pairs(lhttp:formvalue(section)) do + if v and (v ~= "") then + have_http_listener = true + break + end + end + end + if lhttps and lhttps:formvalue(section) and (#(lhttps:formvalue(section)) > 0) then + for k, v in pairs(lhttps:formvalue(section)) do + if v and (v ~= "") then + have_https_listener = true + break + end + end + end + if not (have_http_listener or have_https_listener) then + return nil, "must listen on at list one address:port" + end + return DynamicList.validate(self, value, section) +end + +lhttps = ucs:taboption("general", DynamicList, "listen_https", translate("HTTPS listener (address:port)"), translate("Bind to specific interface:port (by specifying interface address")) +lhttps.datatype = "list(ipaddrport(1))" +lhttps:depends("cert") +lhttps:depends("key") + +function lhttps.validate(self, value, section) + local have_https_listener = false + local have_http_listener = false + if lhttps and lhttps:formvalue(section) and (#(lhttps:formvalue(section)) > 0) then + for k, v in pairs(lhttps:formvalue(section)) do + if v and (v ~= "") then + have_https_listener = true + break + end + end + if have_https_listener and ((not cert_file) or (not cert_file:formvalue(section)) or (cert_file:formvalue(section) == "")) then + return nil, "must have certificate when using https" + end + if have_https_listener and ((not key_file) or (not key_file:formvalue(section)) or (key_file:formvalue(section) == "")) then + return nil, "must have key when using https" + end + end + if lhttp and (lhttp:formvalue(section)) and (#lhttp:formvalue(section) > 0) then + for k, v in pairs(lhttp:formvalue(section)) do + if v and (v ~= "") then + have_http_listener = true + break + end + end + end + if not (have_http_listener or have_https_listener) then + return nil, "must listen on at list one address:port" + end + return DynamicList.validate(self, value, section) +end + +o = ucs:taboption("general", Flag, "redirect_https", translate("Redirect all HTTP to HTTPS")) +o.default = o.enabled +o.rmempty = false + +o = ucs:taboption("general", Flag, "rfc1918_filter", translate("Ignore private IPs on public interface"), translate("Prevent access from private (RFC1918) IPs on an interface if it has an public IP address")) +o.default = o.enabled +o.rmempty = false + +cert_file = ucs:taboption("general", FileUpload, "cert", translate("HTTPS Certificate (DER Encoded)")) + +key_file = ucs:taboption("general", FileUpload, "key", translate("HTTPS Private Key (DER Encoded)")) + +o = ucs:taboption("general", Button, "remove_old", translate("Remove old certificate and key"), + translate("uHTTPd will generate a new self-signed certificate using the configuration shown below.")) +o.inputstyle = "remove" + +function o.write(self, section) + if cert_file:cfgvalue(section) and fs.access(cert_file:cfgvalue(section)) then fs.unlink(cert_file:cfgvalue(section)) end + if key_file:cfgvalue(section) and fs.access(key_file:cfgvalue(section)) then fs.unlink(key_file:cfgvalue(section)) end + luci.sys.call("/etc/init.d/uhttpd restart") + luci.http.redirect(luci.dispatcher.build_url("admin", "services", "uhttpd")) +end + +o = ucs:taboption("general", Button, "remove_conf", translate("Remove configuration for certificate and key"), + translate("This permanently deletes the cert, key, and configuration to use same.")) +o.inputstyle = "remove" + +function o.write(self, section) + if cert_file:cfgvalue(section) and fs.access(cert_file:cfgvalue(section)) then fs.unlink(cert_file:cfgvalue(section)) end + if key_file:cfgvalue(section) and fs.access(key_file:cfgvalue(section)) then fs.unlink(key_file:cfgvalue(section)) end + self.map:del(section, "cert") + self.map:del(section, "key") + self.map:del(section, "listen_https") + luci.http.redirect(luci.dispatcher.build_url("admin", "services", "uhttpd")) +end + +o = ucs:taboption("server", DynamicList, "index_page", translate("Index page(s)"), translate("E.g specify with index.html and index.php when using PHP")) +o.optional = true +o.placeholder = "index.html" + +o = ucs:taboption("server", DynamicList, "interpreter", translate("CGI filetype handler"), translate("Interpreter to associate with file endings ('suffix=handler', e.g. '.php=/usr/bin/php-cgi')")) +o.optional = true + +o = ucs:taboption("server", Flag, "no_symlinks", translate("Do not follow symlinks outside document root")) +o.optional = true + +o = ucs:taboption("server", Flag, "no_dirlists", translate("Do not generate directory listings.")) +o.default = o.disabled + +o = ucs:taboption("server", DynamicList, "alias", translate("Aliases"), translate("(/old/path=/new/path) or (just /old/path which becomes /cgi-prefix/old/path)")) +o.optional = true + +o = ucs:taboption("server", Value, "realm", translate("Realm for Basic Auth")) +o.optional = true +o.placeholder = luci.sys.hostname() or "OpenWrt" + +local httpconfig = ucs:taboption("server", Value, "config", translate("Config file (e.g. for credentials for Basic Auth)"), translate("Will not use HTTP authentication if not present")) +httpconfig.optional = true + +o = ucs:taboption("server", Value, "error_page", translate("404 Error"), translate("Virtual URL or CGI script to display on status '404 Not Found'. Must begin with '/'")) +o.optional = true + +o = ucs:taboption("advanced", Value, "home", translate("Document root"), + translate("Base directory for files to be served")) +o.default = "/www" +o.datatype = "directory" + +o = ucs:taboption("advanced", Value, "cgi_prefix", translate("Path prefix for CGI scripts"), translate("CGI is disabled if not present.")) +o.optional = true + +o = ucs:taboption("advanced", Value, "lua_prefix", translate("Virtual path prefix for Lua scripts")) +o.placeholder = "/lua" +o.optional = true + +o = ucs:taboption("advanced", Value, "lua_handler", translate("Full real path to handler for Lua scripts"), translate("Embedded Lua interpreter is disabled if not present.")) +o.optional = true + +o = ucs:taboption("advanced", Value, "ubus_prefix", translate("Virtual path prefix for ubus via JSON-RPC integration"), translate("ubus integration is disabled if not present")) +o.optional = true + +o = ucs:taboption("advanced", Value, "ubus_socket", translate("Override path for ubus socket")) +o.optional = true + +o = ucs:taboption("advanced", Flag, "ubus_cors", translate("Enable JSON-RPC Cross-Origin Resource Support")) +o.default = o.disabled +o.optional = true + +o = ucs:taboption("advanced", Flag, "no_ubusauth", translate("Disable JSON-RPC authorization via ubus session API")) +o.optional= true +o.default = o.disabled + +o = ucs:taboption("advanced", Value, "script_timeout", translate("Maximum wait time for Lua, CGI, or ubus execution")) +o.placeholder = 60 +o.datatype = "uinteger" +o.optional = true + +o = ucs:taboption("advanced", Value, "network_timeout", translate("Maximum wait time for network activity")) +o.placeholder = 30 +o.datatype = "uinteger" +o.optional = true + +o = ucs:taboption("advanced", Value, "http_keepalive", translate("Connection reuse")) +o.placeholder = 20 +o.datatype = "uinteger" +o.optional = true + +o = ucs:taboption("advanced", Value, "tcp_keepalive", translate("TCP Keepalive")) +o.optional = true +o.datatype = "uinteger" +o.default = 1 + +o = ucs:taboption("advanced", Value, "max_connections", translate("Maximum number of connections")) +o.optional = true +o.datatype = "uinteger" + +o = ucs:taboption("advanced", Value, "max_requests", translate("Maximum number of script requests")) +o.optional = true +o.datatype = "uinteger" + +local s = m:section(NamedSection, "px5g", "cert", translate("uHTTPd Self-signed Certificate Parameters")) + +o = s:option(Value, "days", translate("Valid for # of Days")) +o.default = 730 +o.datatype = "uinteger" + +o = s:option(Value, "bits", translate("Length of key in bits")) +o.default = 1024 +o.datatype = "min(1024)" + +o = s:option(Value, "commonname", translate("Server Hostname"), translate("a.k.a CommonName")) +o.default = luci.sys.hostname() + +o = s:option(Value, "country", translate("Country")) +o.default = "ZZ" + +o = s:option(Value, "state", translate("State")) +o.default = "Unknown" + +o = s:option(Value, "location", translate("Location")) +o.default = "Somewhere" + +return m -- cgit v1.2.3 From 38880407aa6c0246bc9af52073a79ff4a2096a6e Mon Sep 17 00:00:00 2001 From: Daniel Dickinson Date: Mon, 14 Dec 2015 07:38:31 -0500 Subject: modules/luci-base: Fix ipaddrport validator to support ipv6 The previous versiono of ipaddrport validator only worked for ipv4 due to disallowing colons (:) in ip address which obvious fails for ipv6. We now instead allow either ipv4 address or an ipv6 address of the form []:port --- .../luci-base/htdocs/luci-static/resources/cbi.js | 25 ++++++++++++++++++++-- modules/luci-base/luasrc/cbi/datatypes.lua | 18 ++++++++++++++-- 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/modules/luci-base/htdocs/luci-static/resources/cbi.js b/modules/luci-base/htdocs/luci-static/resources/cbi.js index 8a3cb6fca7..6ce947def0 100644 --- a/modules/luci-base/htdocs/luci-static/resources/cbi.js +++ b/modules/luci-base/htdocs/luci-static/resources/cbi.js @@ -172,17 +172,38 @@ var cbi_validators = { return false; }, - 'ipaddrport': function() + 'ip4addrport': function() { var hp = this.split(/:/); if (hp.length == 2) return (cbi_validators.ipaddr.apply(hp[0]) && cbi_validators.port.apply(hp[1])); - return false; }, + 'ipaddrport': function(bracket) + { + if (this.match(/^([^\[\]:]+):([^:]+)$/)) { + var addr = RegExp.$1 + var port = RegExp.$2 + return (cbi_validators.ip4addr.apply(addr) && + cbi_validators.port.apply(port)); + } else if ((bracket == 1) && (this.match(/^\[(.+)\]:([^:]+)$/))) { + var addr = RegExp.$1 + var port = RegExp.$2 + return (cbi_validators.ip6addr.apply(addr) && + cbi_validators.port.apply(port)); + } else if ((bracket != 1) && (this.match(/^([^\[\]]+):([^:]+)$/))) { + var addr = RegExp.$1 + var port = RegExp.$2 + return (cbi_validators.ip6addr.apply(addr) && + cbi_validators.port.apply(port)); + } else { + return false; + } + }, + 'wpakey': function() { var v = this; diff --git a/modules/luci-base/luasrc/cbi/datatypes.lua b/modules/luci-base/luasrc/cbi/datatypes.lua index 52f90afee6..aafdc5c3c6 100644 --- a/modules/luci-base/luasrc/cbi/datatypes.lua +++ b/modules/luci-base/luasrc/cbi/datatypes.lua @@ -189,9 +189,23 @@ function hostport(val) return not not (h and p and host(h) and port(p)) end -function ipaddrport(val) +function ip4addrport(val) local h, p = val:match("^([^:]+):([^:]+)$") - return not not (h and p and ipaddr(h) and port(p)) + return (h and p and ip4addr(h) and port(p)) +end + +function ipaddrport(val, bracket) + local h, p = val:match("^([^%[%]:]+):([^:]+)$") + if (h and p and ip4addr(h) and port(p)) then + return true + elseif (bracket == 1) then + h, p = val:match("^(%[.+%]):([^:]+)$") + if (h and p and ip6addr(h) and port(p)) then + return true + end + end + h, p = val:match("^([^%[%]]+):([^:]+)$") + return (h and p and ip6addr(h) and port(p)) end function wpakey(val) -- cgit v1.2.3 From 0ec3f91a0c7a8c71980f5e70f3322397e496a8d6 Mon Sep 17 00:00:00 2001 From: Daniel Dickinson Date: Mon, 14 Dec 2015 22:51:29 -0500 Subject: validation: Add option ipv4only option to host and hostport datatypes Some applications only support ipv4 so add ipv4only option to host and hostport datatypes so that for thos applications that when an IP address is specified only and ipv4 ip address gets accepted. --- .../luasrc/model/cbi/pbx-voicemail.lua | 2 +- .../luci-app-pbx/luasrc/model/cbi/pbx-advanced.lua | 2 +- applications/luci-app-pbx/luasrc/model/cbi/pbx-voip.lua | 6 +++--- .../luasrc/model/cbi/luci_statistics/olsrd.lua | 2 +- .../luci-app-tinyproxy/luasrc/model/cbi/tinyproxy.lua | 3 ++- .../luasrc/model/cbi/watchcat/watchcat.lua | 2 +- modules/luci-base/htdocs/luci-static/resources/cbi.js | 9 +++++---- modules/luci-base/luasrc/cbi/datatypes.lua | 15 ++++++++++----- .../luasrc/model/cbi/admin_network/dhcp.lua | 2 +- .../luasrc/model/cbi/admin_network/wifi.lua | 4 ++-- .../luasrc/model/cbi/admin_system/system.lua | 2 +- .../luasrc/model/cbi/admin_network/proto_aiccu.lua | 2 +- .../luasrc/model/cbi/admin_network/proto_openconnect.lua | 2 +- .../luasrc/model/cbi/admin_network/proto_l2tp.lua | 2 +- .../luasrc/model/cbi/admin_network/proto_pppossh.lua | 2 +- .../luasrc/model/cbi/admin_network/proto_pptp.lua | 2 +- 16 files changed, 33 insertions(+), 26 deletions(-) diff --git a/applications/luci-app-pbx-voicemail/luasrc/model/cbi/pbx-voicemail.lua b/applications/luci-app-pbx-voicemail/luasrc/model/cbi/pbx-voicemail.lua index 9ff2ed97fe..a6087e9aec 100644 --- a/applications/luci-app-pbx-voicemail/luasrc/model/cbi/pbx-voicemail.lua +++ b/applications/luci-app-pbx-voicemail/luasrc/model/cbi/pbx-voicemail.lua @@ -92,7 +92,7 @@ s = m:section(NamedSection, "voicemail_smtp", "voicemail", translate("Outgoing m s.anonymous = true serv = s:option(Value, "smtp_server", translate("SMTP Server Hostname or IP Address")) -serv.datatype = "host" +serv.datatype = "host(0)" port = s:option(Value, "smtp_port", translate("SMTP Port Number")) port.datatype = "port" diff --git a/applications/luci-app-pbx/luasrc/model/cbi/pbx-advanced.lua b/applications/luci-app-pbx/luasrc/model/cbi/pbx-advanced.lua index 5d4f135c5b..34288c6632 100644 --- a/applications/luci-app-pbx/luasrc/model/cbi/pbx-advanced.lua +++ b/applications/luci-app-pbx/luasrc/model/cbi/pbx-advanced.lua @@ -264,7 +264,7 @@ h = s:taboption("remote_usage", Value, "externhost", translate("Domain/IP Addres 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" +h.datatype = "host(0)" 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. \ diff --git a/applications/luci-app-pbx/luasrc/model/cbi/pbx-voip.lua b/applications/luci-app-pbx/luasrc/model/cbi/pbx-voip.lua index ed1ed1edb1..9b46202855 100644 --- a/applications/luci-app-pbx/luasrc/model/cbi/pbx-voip.lua +++ b/applications/luci-app-pbx/luasrc/model/cbi/pbx-voip.lua @@ -84,7 +84,7 @@ function pwd.write(self, section, value) end h = s:option(Value, "host", translate("SIP Server/Registrar")) -h.datatype = "host" +h.datatype = "host(0)" 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\) \ @@ -103,7 +103,7 @@ p.default = "yes" from = s:option(Value, "fromdomain", translate("SIP Realm (needed by some providers)")) from.optional = true -from.datatype = "host" +from.datatype = "host(0)" port = s:option(Value, "port", translate("SIP Server/Registrar Port")) port.optional = true @@ -111,6 +111,6 @@ port.datatype = "port" op = s:option(Value, "outboundproxy", translate("Outbound Proxy")) op.optional = true -op.datatype = "host" +op.datatype = "host(0)" return m diff --git a/applications/luci-app-statistics/luasrc/model/cbi/luci_statistics/olsrd.lua b/applications/luci-app-statistics/luasrc/model/cbi/luci_statistics/olsrd.lua index 59266e553c..950d7a7971 100644 --- a/applications/luci-app-statistics/luasrc/model/cbi/luci_statistics/olsrd.lua +++ b/applications/luci-app-statistics/luasrc/model/cbi/luci_statistics/olsrd.lua @@ -12,7 +12,7 @@ enable.default = 0 host = s:option(Value, "Host", translate("Host"), translate("IP or hostname where to get the txtinfo output from")) host.placeholder = "127.0.0.1" -host.datatype = "host" +host.datatype = "host(1)" host.rmempty = true port = s:option(Value, "Port", translate("Port")) diff --git a/applications/luci-app-tinyproxy/luasrc/model/cbi/tinyproxy.lua b/applications/luci-app-tinyproxy/luasrc/model/cbi/tinyproxy.lua index 19bdd9afc1..11f34fbad4 100644 --- a/applications/luci-app-tinyproxy/luasrc/model/cbi/tinyproxy.lua +++ b/applications/luci-app-tinyproxy/luasrc/model/cbi/tinyproxy.lua @@ -227,7 +227,7 @@ ta = s:option(Value, "target", translate("Target host"), ta.rmempty = true ta.placeholder = "0.0.0.0/0" -ta.datatype = "host" +ta.datatype = "host(1)" v = s:option(Value, "via", translate("Via proxy"), @@ -235,5 +235,6 @@ v = s:option(Value, "via", translate("Via proxy"), v:depends({type="proxy"}) v.placeholder = "10.0.0.1:8080" +v.datatype = "ip4addrport" return m diff --git a/applications/luci-app-watchcat/luasrc/model/cbi/watchcat/watchcat.lua b/applications/luci-app-watchcat/luasrc/model/cbi/watchcat/watchcat.lua index 883416b219..f64370bfe3 100644 --- a/applications/luci-app-watchcat/luasrc/model/cbi/watchcat/watchcat.lua +++ b/applications/luci-app-watchcat/luasrc/model/cbi/watchcat/watchcat.lua @@ -38,7 +38,7 @@ period = s:option(Value, "period", pinghost = s:option(Value, "pinghosts", translate("Ping host"), translate("Host address to ping")) -pinghost.datatype = "host" +pinghost.datatype = "host(1)" pinghost.default = "8.8.8.8" pinghost:depends({mode="ping"}) diff --git a/modules/luci-base/htdocs/luci-static/resources/cbi.js b/modules/luci-base/htdocs/luci-static/resources/cbi.js index 6ce947def0..8159a378fc 100644 --- a/modules/luci-base/htdocs/luci-static/resources/cbi.js +++ b/modules/luci-base/htdocs/luci-static/resources/cbi.js @@ -139,10 +139,11 @@ var cbi_validators = { return (this.match(/^([a-fA-F0-9]{2}:){5}[a-fA-F0-9]{2}$/) != null); }, - 'host': function() + 'host': function(ipv4only) { return cbi_validators.hostname.apply(this) || - cbi_validators.ipaddr.apply(this); + ((ipv4only != 1) && cbi_validators.ipaddr.apply(this)) || + ((ipv4only == 1) && cb_validators.ip4addr.apply(this)); }, 'hostname': function() @@ -161,12 +162,12 @@ var cbi_validators = { cbi_validators.host.apply(this); }, - 'hostport': function() + 'hostport': function(ipv4only) { var hp = this.split(/:/); if (hp.length == 2) - return (cbi_validators.host.apply(hp[0]) && + return (cbi_validators.host.apply(hp[0], ipv4only) && cbi_validators.port.apply(hp[1])); return false; diff --git a/modules/luci-base/luasrc/cbi/datatypes.lua b/modules/luci-base/luasrc/cbi/datatypes.lua index aafdc5c3c6..d415c6f58c 100644 --- a/modules/luci-base/luasrc/cbi/datatypes.lua +++ b/modules/luci-base/luasrc/cbi/datatypes.lua @@ -176,17 +176,22 @@ function hostname(val) return false end -function host(val) - return hostname(val) or ipaddr(val) +function host(val, ipv4only) + return hostname(val) or ((ipv4only == 1) and ip4addr(val)) or ((not (ipv4only == 1)) and ipaddr(val)) end function network(val) return uciname(val) or host(val) end -function hostport(val) +function hostport(val, ipv4only) local h, p = val:match("^([^:]+):([^:]+)$") - return not not (h and p and host(h) and port(p)) + return not not (h and p and host(h, ipv4only) and port(p)) +end + +function ip4addrport(val, bracket) + local h, p = val:match("^([^:]+):([^:]+)$") + return (h and p and ip4addr(h) and port(p)) end function ip4addrport(val) @@ -199,7 +204,7 @@ function ipaddrport(val, bracket) if (h and p and ip4addr(h) and port(p)) then return true elseif (bracket == 1) then - h, p = val:match("^(%[.+%]):([^:]+)$") + h, p = val:match("^%[(.+)%]:([^:]+)$") if (h and p and ip6addr(h) and port(p)) then return true end diff --git a/modules/luci-mod-admin-full/luasrc/model/cbi/admin_network/dhcp.lua b/modules/luci-mod-admin-full/luasrc/model/cbi/admin_network/dhcp.lua index 4dc52ada06..572446feff 100644 --- a/modules/luci-mod-admin-full/luasrc/model/cbi/admin_network/dhcp.lua +++ b/modules/luci-mod-admin-full/luasrc/model/cbi/admin_network/dhcp.lua @@ -135,7 +135,7 @@ rd = s:taboption("general", DynamicList, "rebind_domain", translate("List of domains to allow RFC1918 responses for")) rd:depends("rebind_protection", "1") -rd.datatype = "host" +rd.datatype = "host(1)" rd.placeholder = "ihost.netflix.com" diff --git a/modules/luci-mod-admin-full/luasrc/model/cbi/admin_network/wifi.lua b/modules/luci-mod-admin-full/luasrc/model/cbi/admin_network/wifi.lua index b91c29b088..44528927e7 100644 --- a/modules/luci-mod-admin-full/luasrc/model/cbi/admin_network/wifi.lua +++ b/modules/luci-mod-admin-full/luasrc/model/cbi/admin_network/wifi.lua @@ -749,7 +749,7 @@ auth_server:depends({mode="ap", encryption="wpa2"}) auth_server:depends({mode="ap-wds", encryption="wpa"}) auth_server:depends({mode="ap-wds", encryption="wpa2"}) auth_server.rmempty = true -auth_server.datatype = "host" +auth_server.datatype = "host(0)" auth_port = s:taboption("encryption", Value, "auth_port", translate("Radius-Authentication-Port"), translatef("Default %d", 1812)) auth_port:depends({mode="ap", encryption="wpa"}) @@ -773,7 +773,7 @@ acct_server:depends({mode="ap", encryption="wpa2"}) acct_server:depends({mode="ap-wds", encryption="wpa"}) acct_server:depends({mode="ap-wds", encryption="wpa2"}) acct_server.rmempty = true -acct_server.datatype = "host" +acct_server.datatype = "host(0)" acct_port = s:taboption("encryption", Value, "acct_port", translate("Radius-Accounting-Port"), translatef("Default %d", 1813)) acct_port:depends({mode="ap", encryption="wpa"}) diff --git a/modules/luci-mod-admin-full/luasrc/model/cbi/admin_system/system.lua b/modules/luci-mod-admin-full/luasrc/model/cbi/admin_system/system.lua index 94ba8053ad..2874b5607e 100644 --- a/modules/luci-mod-admin-full/luasrc/model/cbi/admin_system/system.lua +++ b/modules/luci-mod-admin-full/luasrc/model/cbi/admin_system/system.lua @@ -204,7 +204,7 @@ if has_ntpd then o = s:option(DynamicList, "server", translate("NTP server candidates")) - o.datatype = "host" + o.datatype = "host(0)" o:depends("enable", "1") -- retain server list even if disabled diff --git a/protocols/luci-proto-ipv6/luasrc/model/cbi/admin_network/proto_aiccu.lua b/protocols/luci-proto-ipv6/luasrc/model/cbi/admin_network/proto_aiccu.lua index 9315dc25bf..7b5e6043cc 100644 --- a/protocols/luci-proto-ipv6/luasrc/model/cbi/admin_network/proto_aiccu.lua +++ b/protocols/luci-proto-ipv6/luasrc/model/cbi/admin_network/proto_aiccu.lua @@ -39,7 +39,7 @@ protocol.optional = true server = section:taboption("general", Value, "server", translate("Tunnel setup server"), translate("Optional, specify to override default server (tic.sixxs.net)")) -server.datatype = "host" +server.datatype = "host(0)" server.optional = true diff --git a/protocols/luci-proto-openconnect/luasrc/model/cbi/admin_network/proto_openconnect.lua b/protocols/luci-proto-openconnect/luasrc/model/cbi/admin_network/proto_openconnect.lua index 4ed34ae3bc..ad3f2edf0b 100644 --- a/protocols/luci-proto-openconnect/luasrc/model/cbi/admin_network/proto_openconnect.lua +++ b/protocols/luci-proto-openconnect/luasrc/model/cbi/admin_network/proto_openconnect.lua @@ -13,7 +13,7 @@ oc_key_file = "/etc/openconnect/user-key-" .. ifc .. ".pem" oc_ca_file = "/etc/openconnect/ca-" .. ifc .. ".pem" server = section:taboption("general", Value, "server", translate("VPN Server")) -server.datatype = "host" +server.datatype = "host(0)" port = section:taboption("general", Value, "port", translate("VPN Server port")) port.placeholder = "443" diff --git a/protocols/luci-proto-ppp/luasrc/model/cbi/admin_network/proto_l2tp.lua b/protocols/luci-proto-ppp/luasrc/model/cbi/admin_network/proto_l2tp.lua index 5e8b3fcaae..506170c2de 100644 --- a/protocols/luci-proto-ppp/luasrc/model/cbi/admin_network/proto_l2tp.lua +++ b/protocols/luci-proto-ppp/luasrc/model/cbi/admin_network/proto_l2tp.lua @@ -8,7 +8,7 @@ local ipv6, defaultroute, metric, peerdns, dns, mtu server = section:taboption("general", Value, "server", translate("L2TP Server")) -server.datatype = "or(host, hostport)" +server.datatype = "or(host(1), hostport(1))" username = section:taboption("general", Value, "username", translate("PAP/CHAP username")) diff --git a/protocols/luci-proto-ppp/luasrc/model/cbi/admin_network/proto_pppossh.lua b/protocols/luci-proto-ppp/luasrc/model/cbi/admin_network/proto_pppossh.lua index c93353af90..eca7bdd4d3 100644 --- a/protocols/luci-proto-ppp/luasrc/model/cbi/admin_network/proto_pppossh.lua +++ b/protocols/luci-proto-ppp/luasrc/model/cbi/admin_network/proto_pppossh.lua @@ -8,7 +8,7 @@ local sshuser, server, port, ssh_options, identity, ipaddr, peeraddr sshuser = section:taboption("general", Value, "sshuser", translate("SSH username")) server = section:taboption("general", Value, "server", translate("SSH server address")) -server.datatype = "host" +server.datatype = "host(0)" port = section:taboption("general", Value, "port", translate("SSH server port")) port.datatype = "port" diff --git a/protocols/luci-proto-ppp/luasrc/model/cbi/admin_network/proto_pptp.lua b/protocols/luci-proto-ppp/luasrc/model/cbi/admin_network/proto_pptp.lua index d81db1faee..462d7019f0 100644 --- a/protocols/luci-proto-ppp/luasrc/model/cbi/admin_network/proto_pptp.lua +++ b/protocols/luci-proto-ppp/luasrc/model/cbi/admin_network/proto_pptp.lua @@ -9,7 +9,7 @@ local defaultroute, metric, peerdns, dns, server = section:taboption("general", Value, "server", translate("VPN Server")) -server.datatype = "host" +server.datatype = "host(0)" username = section:taboption("general", Value, "username", translate("PAP/CHAP username")) -- cgit v1.2.3 From b130ca554f13e17c787a1c6fd09e67dd7727a4d3 Mon Sep 17 00:00:00 2001 From: Daniel Dickinson Date: Tue, 15 Dec 2015 11:37:33 -0500 Subject: lib-nixio / luci-base: Fix for reading csrf token prevents file upload The call to http.formvalue in order to read the csrf token causes _parse_input to be triggered *before* controllers and cbi maps have been built. This results in the failure of file uploads because the file handler is not yet in place when _parse_input gets called, and it is in _parse_input that POST data is parsed (including files). To fix this we add the ability to write file fields to temporary files (using mkstemp and unlink in nixio.file) and use this to store file data until the filehandler is registered, with a fallback to reading the file data into memory. Once the filehandler callback gets registered we iterate though all previously parsed (saved) files and copy the data to the file handler, and then close the temporary file (which finally removes because we unlinked after creating the file, but didn't close the file so unlink was deferred). Signed-off-by: Daniel Dickinson --- libs/luci-lib-nixio/src/file.c | 33 ++++++++++++++++++++++++++++++ modules/luci-base/luasrc/http.lua | 31 ++++++++++++++++++++++++++++ modules/luci-base/luasrc/http/protocol.lua | 26 +++++++++++++++++++++++ 3 files changed, 90 insertions(+) diff --git a/libs/luci-lib-nixio/src/file.c b/libs/luci-lib-nixio/src/file.c index b86e040e1d..cfa35dfd17 100644 --- a/libs/luci-lib-nixio/src/file.c +++ b/libs/luci-lib-nixio/src/file.c @@ -79,6 +79,38 @@ static int nixio_open(lua_State *L) { return 1; } +static int nixio_mkstemp(lua_State *L) { + const char *intemplate = luaL_checklstring(L, 1, NULL); + size_t len = lua_strlen(L, 1); + char *template = (char *)lua_newuserdata(L, 13 + len); + if (!template) { + return luaL_error(L, "out of memory"); + } + snprintf(template, 13 + len, "/tmp/%s.XXXXXX", intemplate); + + int fd; + + do { + fd = mkstemp(template); + } while (fd == -1 && errno == EINTR); + if (fd == -1) { + return nixio__perror(L); + } + unlink(template); + + int *udata = lua_newuserdata(L, sizeof(int)); + if (!udata) { + return luaL_error(L, "out of memory"); + } + + *udata = fd; + + luaL_getmetatable(L, NIXIO_FILE_META); + lua_setmetatable(L, -2); + + return 1; +} + static int nixio_open_flags(lua_State *L) { int mode = 0; const int j = lua_gettop(L); @@ -366,6 +398,7 @@ static const luaL_reg R[] = { {"dup", nixio_dup}, {"open", nixio_open}, {"open_flags", nixio_open_flags}, + {"mkstemp", nixio_mkstemp}, {"pipe", nixio_pipe}, {NULL, NULL} }; diff --git a/modules/luci-base/luasrc/http.lua b/modules/luci-base/luasrc/http.lua index 4b35731727..8795dfc4b2 100644 --- a/modules/luci-base/luasrc/http.lua +++ b/modules/luci-base/luasrc/http.lua @@ -89,6 +89,37 @@ end function Request.setfilehandler(self, callback) self.filehandler = callback + + -- If input has already been parsed then any files are either in temporary files + -- or are in self.message.params[key] + if self.parsed_input then + for param, value in pairs(self.message.params) do + repeat + -- We're only interested in files + if (not value["file"]) then break end + -- If we were able to write to temporary file + if (value["fd"]) then + fd = value["fd"] + local eof = false + repeat + filedata = fd:read(1024) + if (filedata:len() < 1024) then + eof = true + end + callback({ name=value["name"], file=value["file"] }, filedata, eof) + until (eof) + fd:close() + value["fd"] = nil + -- We had to read into memory + else + -- There should only be one numbered value in table - the data + for k, v in ipairs(value) do + callback({ name=value["name"], file=value["file"] }, v, true) + end + end + until true + end + end end function Request._parse_input(self) diff --git a/modules/luci-base/luasrc/http/protocol.lua b/modules/luci-base/luasrc/http/protocol.lua index 0cb62aeec9..061c6ad544 100644 --- a/modules/luci-base/luasrc/http/protocol.lua +++ b/modules/luci-base/luasrc/http/protocol.lua @@ -113,6 +113,16 @@ local function __initval( tbl, key ) end end +-- (Internal function) +-- Initialize given file parameter. +local function __initfileval( tbl, key, filename, fd ) + if tbl[key] == nil then + tbl[key] = { file=filename, fd=fd, name=key, "" } + else + table.insert( tbl[key], "" ) + end +end + -- (Internal function) -- Append given data to given parameter, either by extending the string value -- or by appending it to the last string in the parameter's value table. @@ -313,6 +323,22 @@ function mimedecode_message_body( src, msg, filecb ) __appendval( msg.params, field.name, field.file ) store = filecb + elseif field.name and field.file then + local nxf = require "nixio" + local fd = nxf.mkstemp(field.name) + __initfileval ( msg.params, field.name, field.file, fd ) + if fd then + store = function(hdr, buf, eof) + fd:write(buf) + if (eof) then + fd:seek(0, "set") + end + end + else + store = function( hdr, buf, eof ) + __appendval( msg.params, field.name, buf ) + end + end elseif field.name then __initval( msg.params, field.name ) -- cgit v1.2.3