Browse Source

luci-app-smartdns: rewrite luci-app in JavaScript

Nick Peng 5 năm trước cách đây
mục cha
commit
80174d9da5

+ 8 - 0
package/luci/control/control

@@ -0,0 +1,8 @@
+Package: luci-app-smartdns
+Version: git-18.201.27126-7bf0367-1
+Depends: libc, smartdns
+Source: feeds/luci/applications/luci-app-smartdns
+Section: luci
+Architecture: all
+Installed-Size: 1040
+Description:  A smartdns server module

+ 21 - 0
package/luci/control/postinst

@@ -0,0 +1,21 @@
+#!/bin/sh
+#
+# Copyright (C) 2018-2020 Ruilin Peng (Nick) <[email protected]>.
+#
+# smartdns is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# smartdns is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+[ "${IPKG_NO_SCRIPT}" = "1" ] && exit 0
+[ -x ${IPKG_INSTROOT}/lib/functions.sh ] || exit 0
+. ${IPKG_INSTROOT}/lib/functions.sh
+default_postinst $0 $@

+ 4 - 0
package/luci/control/postinst-pkg

@@ -0,0 +1,4 @@
+[ -n "${IPKG_INSTROOT}" ] || {
+	(. /etc/uci-defaults/50_luci-smartdns) && rm -f /etc/uci-defaults/50_luci-smartdns
+	exit 0
+}

+ 20 - 0
package/luci/control/prerm

@@ -0,0 +1,20 @@
+#!/bin/sh
+#
+# Copyright (C) 2018-2020 Ruilin Peng (Nick) <[email protected]>.
+#
+# smartdns is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# smartdns is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+[ -x ${IPKG_INSTROOT}/lib/functions.sh ] || exit 0
+. ${IPKG_INSTROOT}/lib/functions.sh
+default_prerm $0 $@

+ 1 - 0
package/luci/debian-binary

@@ -0,0 +1 @@
+2.0

+ 26 - 0
package/luci/files/etc/uci-defaults/50_luci-smartdns

@@ -0,0 +1,26 @@
+#!/bin/sh
+#
+# Copyright (C) 2018-2020 Ruilin Peng (Nick) <[email protected]>.
+#
+# smartdns is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# smartdns is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+uci -q batch <<-EOF >/dev/null
+	delete ucitrack.@smartdns[-1]
+	add ucitrack smartdns
+	set ucitrack.@smartdns[-1].init=smartdns
+	commit ucitrack
+EOF
+
+rm -f /tmp/luci-indexcache
+exit 0

+ 27 - 0
package/luci/files/luci/controller/smartdns.lua

@@ -0,0 +1,27 @@
+--
+-- Copyright (C) 2018-2020 Ruilin Peng (Nick) <[email protected]>.
+--
+-- smartdns is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- smartdns is distributed in the hope that it will be useful,
+-- but WITHOUT ANY WARRANTY; without even the implied warranty of
+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+-- GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License
+-- along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+module("luci.controller.smartdns", package.seeall)
+
+function index()
+	if not nixio.fs.access("/etc/config/smartdns") then
+		return
+	end
+
+	local page
+	page = entry({"admin", "services", "smartdns"}, view("smartdns/smartdns"), _("SmartDNS"), 60)
+	page.dependent = true
+end

+ 476 - 0
package/luci/files/luci/htdocs/luci-static/resources/view/smartdns/smartdns.js

@@ -0,0 +1,476 @@
+/*************************************************************************
+ *
+ * Copyright (C) 2018-2020 Ruilin Peng (Nick) <[email protected]>.
+ *
+ * smartdns is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * smartdns is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+'use strict';
+'require fs';
+'require uci';
+'require form';
+'require rpc';
+
+var conf = 'smartdns';
+var callServiceList = rpc.declare({
+	object: 'service',
+	method: 'list',
+	params: ['name'],
+	expect: { '': {} }
+});
+
+function getPidOfSmartdns() {
+	return L.resolveDefault(callServiceList(conf), {})
+		.then(function (res) {
+			var isrunning = false;
+			try {
+				isrunning = res[conf]['instances']['smartdns']['running'];
+			} catch (e) { }
+			return isrunning;
+		});
+}
+
+function getIPTablesRedirect() {
+	return fs.exec('/usr/sbin/iptables', ['-t', 'nat', '-nL', 'PREROUTING']).then(function (res) {
+		if (res.code === 0) {
+			return res.stdout.trim();
+		} else {
+			return "";
+		}
+	});
+}
+
+function getIP6TablesRedirect() {
+	return fs.exec('/usr/sbin/ip6tables', ['-t', 'nat', '-nL', 'PREROUTING']).then(function (res) {
+		if (res.code === 0) {
+			return res.stdout.trim();
+		} else {
+			return "";
+		}
+	});
+}
+
+function smartdnsServiceStatus() {
+	return Promise.all([
+		getPidOfSmartdns(),
+		getIPTablesRedirect(),
+		getIP6TablesRedirect()
+	]);
+}
+
+function smartdnsRenderStatus(res) {
+	var renderHTML = "";
+	var isRunning = res[0];
+	var ipt = res[1];
+	var ip6t = res[2];
+
+	var serverPort = uci.get_first('smartdns', 'smartdns', 'port');
+	var redirectMode = uci.get_first('smartdns', 'smartdns', 'redirect');
+	var ipv6Enabled = uci.get_first('smartdns', 'smartdns', 'ipv6_server');
+
+	if (isRunning) {
+		renderHTML += "<b><font color=green>SmartDNS - " + _("RUNNING") + "</font></b></em>";
+	} else {
+		renderHTML += "<b><font color=red>SmartDNS - " + _("NOT RUNNING") + "</font></b></em>";
+	}
+
+	if (redirectMode === "dnsmasq-upstream") {
+		var matchLine = "127.0.0.1#" + serverPort;
+		var dnsmasqServer = uci.get_first('dhcp', 'dnsmasq', 'server') || "";
+
+		if (dnsmasqServer.indexOf(matchLine) < 0) {
+			renderHTML += "<br></br><b><font color=red>" + _("Dnsmasq Forwared To Smartdns Failure") + "</font></b>";
+		}
+	} else if (redirectMode === "redirect") {
+		var redirectRules = (ipt || '').split(/\n/).filter(function (rule) {
+			return rule.match(/REDIRECT/) && rule.match(/dpt:53/) && rule.match("ports " + serverPort);
+		});
+
+		if (redirectRules.length <= 0) {
+			renderHTML += "<br></br><b><font color=red>" + _("IPV4 53 Port Redirect Failure") + "</font></b>";
+			if (ipv6Enabled) {
+				var redirectRules = (ip6t || '').split(/\n/).filter(function (rule) {
+					return rule.match(/REDIRECT/) && rule.match(/dpt:53/) && rule.match("ports " + serverPort);
+				});
+				if (redirectRules.length <= 0) {
+					renderHTML += "<br></br><b><font color=red>" + _("IPV6 53 Port Redirect Failure") + "</font></b>";
+				}
+			}
+		}
+	}
+
+	return renderHTML;
+}
+
+return L.view.extend({
+	load: function () {
+		return Promise.all([
+			L.resolveDefault(fs.stat('/etc/config/smartdns'), null),
+			L.resolveDefault(fs.stat('/usr/sbin/smartdns'), {}),
+			uci.load('smartdns'),
+			uci.load('dhcp')
+		]);
+	},
+	render: function (stats) {
+		var m, s, o;
+
+		m = new form.Map('smartdns', _('SmartDNS'));
+		m.title = _("SmartDNS Server");
+		m.description = _("SmartDNS is a local high-performance DNS server, supports finding fastest IP, "
+			+ "supports ad filtering, and supports avoiding DNS poisoning.");
+
+		s = m.section(form.NamedSection, '_status');
+		s.anonymous = true;
+		s.render = function (section_id) {
+			L.Poll.add(function () {
+				return L.resolveDefault(smartdnsServiceStatus()).then(function (res) {
+					var view = document.getElementById("service_status");
+					view.innerHTML = smartdnsRenderStatus(res);
+				});
+			});
+
+			return E('div', { class: 'cbi-map' },
+				E('div', { class: 'cbi-section' }, [
+					E('div', { id: 'service_status' },
+						_('Collecting data ...'))
+				])
+			);
+		}
+
+		// Basic;
+		s = m.section(form.TypedSection, "smartdns", _("Settings"), _("General Settings"));
+		s.anonymous = true;
+
+		s.tab("settings", _("General Settings"));
+		s.tab("seconddns", _("Second Server Settings"));
+		s.tab("custom", _("Custom Settings"));
+
+		// Eanble;
+		o = s.taboption("settings", form.Flag, "enabled", _("Enable"), _("Enable or disable smartdns server"));
+		o.default = o.disabled;
+		o.rempty = false;
+
+		// server name;
+		o = s.taboption("settings", form.Value, "server_name", _("Server Name"), _("Smartdns server name"));
+		o.default = "smartdns";
+		o.datatype = "hostname";
+		o.rempty = false;
+
+		// Port;
+		o = s.taboption("settings", form.Value, "port", _("Local Port"), _("Smartdns local server port"));
+		o.placeholder = 6053;
+		o.default = 6053;
+		o.datatype = "port";
+		o.rempty = false;
+
+		// Enable TCP server;
+		o = s.taboption("settings", form.Flag, "tcp_server", _("TCP Server"), _("Enable TCP DNS Server"));
+		o.rmempty = false;
+		o.default = o.enabled;
+
+		// Support IPV6;
+		o = s.taboption("settings", form.Flag, "ipv6_server", _("IPV6 Server"), _("Enable IPV6 DNS Server"));
+		o.rmempty = false;
+		o.default = o.enabled;
+
+		// Support DualStack ip selection;
+		o = s.taboption("settings", form.Flag, "dualstack_ip_selection", _("Dual-stack IP Selection"),
+			_("Enable IP selection between IPV4 and IPV6"));
+		o.rmempty = false;
+		o.default = o.disabled;
+
+		// Domain prefetch load ;
+		o = s.taboption("settings", form.Flag, "prefetch_domain", _("Domain prefetch"),
+			_("Enable domain prefetch, accelerate domain response speed."));
+		o.rmempty = false;
+		o.default = o.disabled;
+
+		// Redirect;
+		o = s.taboption("settings", form.ListValue, "redirect", _("Redirect"), _("SmartDNS redirect mode"));
+		o.placeholder = "none";
+		o.value("none", _("none"));
+		o.value("dnsmasq-upstream", _("Run as dnsmasq upstream server"));
+		o.value("redirect", _("Redirect 53 port to SmartDNS"));
+		o.default = "none";
+		o.rempty = false;
+
+		// cache-size;
+		o = s.taboption("settings", form.Value, "cache_size", _("Cache Size"), _("DNS domain result cache size"));
+		o.rempty = true;
+
+		// rr-ttl;
+		o = s.taboption("settings", form.Value, "rr_ttl", _("Domain TTL"), _("TTL for all domain result."));
+		o.rempty = true;
+
+		// rr-ttl-min;
+		o = s.taboption("settings", form.Value, "rr_ttl_min", _("Domain TTL Min"),
+			_("Minimum TTL for all domain result."));
+		o.rempty = true;
+		o.placeholder = "300";
+		o.default = 300;
+		o.optional = true;
+
+		// second dns server;
+		// rr-ttl-max;
+		o = s.taboption("settings", form.Value, "rr_ttl_max", _("Domain TTL Max"),
+			_("Maximum TTL for all domain result."));
+		o.rempty = true;
+
+		// Eanble;
+		o = s.taboption("seconddns", form.Flag, "seconddns_enabled", _("Enable"),
+			_("Enable or disable second DNS server."));
+		o.default = o.disabled;
+		o.rempty = false;
+
+		// Port;
+		o = s.taboption("seconddns", form.Value, "seconddns_port", _("Local Port"), _("Smartdns local server port"));
+		o.placeholder = 7053;
+		o.default = 7053;
+		o.datatype = "port";
+		o.rempty = false;
+
+		// Enable TCP server;
+		o = s.taboption("seconddns", form.Flag, "seconddns_tcp_server", _("TCP Server"), _("Enable TCP DNS Server"));
+		o.rmempty = false;
+		o.default = o.enabled;
+
+		o = s.taboption("seconddns", form.Flag, "seconddns_no_speed_check", _("Skip Speed Check"),
+			_("Do not check speed."));
+		o.rmempty = false;
+		o.default = o.disabled;
+
+		// dns server group;
+		o = s.taboption("seconddns", form.Value, "seconddns_server_group", _("Server Group"),
+			_("Query DNS through specific dns server group, such as office, home."));
+		o.rmempty = true;
+		o.placeholder = "default";
+		o.datatype = "hostname";
+		o.rempty = true;
+
+		// skip address rules;
+		o = s.taboption("seconddns", form.Flag, "seconddns_no_rule_addr", _("Skip Address Rules"),
+			_("Skip address rules."));
+		o.rmempty = false;
+		o.default = o.disabled;
+
+		// skip name server rules;
+		o = s.taboption("seconddns", form.Flag, "seconddns_no_rule_nameserver", _("Skip Nameserver Rule"),
+			_("Skip nameserver rules."));
+		o.rmempty = false;
+		o.default = o.disabled;
+
+		// skip ipset rules;
+		o = s.taboption("seconddns", form.Flag, "seconddns_no_rule_ipset", _("Skip Ipset Rule"),
+			_("Skip ipset rules."));
+		o.rmempty = false;
+		o.default = o.disabled;
+
+		// skip soa address rule;
+		o = s.taboption("seconddns", form.Flag, "seconddns_no_rule_soa", _("Skip SOA Address Rule"),
+			_("Skip SOA address rules."));
+		o.rmempty = false;
+		o.default = o.disabled;
+
+		o = s.taboption("seconddns", form.Flag, "seconddns_no_dualstack_selection", _("Skip Dualstack Selection"),
+			_("Skip Sualstack Selection."));
+		o.rmempty = false;
+		o.default = o.disabled;
+
+		// skip cache;
+		o = s.taboption("seconddns", form.Flag, "seconddns_no_cache", _("Skip Cache"), _("Skip Cache."));
+		o.rmempty = false;
+		o.default = o.disabled;
+
+		// custom settings;
+		o = s.taboption("custom", form.TextValue, "custom_conf",
+			_(""),
+			_("smartdns custom settings"));
+
+		o.rows = 20;
+		o.cfgvalue = function (section_id) {
+			return fs.trimmed('/etc/smartdns/custom.conf');
+		};
+		o.write = function (section_id, formvalue) {
+			return fs.write('/etc/smartdns/custom.conf', formvalue.trim().replace(/\r\n/g, '\n') + '\n');
+		};
+
+		o = s.taboption("custom", form.Flag, "coredump", _("Generate Coredump"),
+			_("Generate Coredump file when smartdns crash, coredump file is located at /tmp/smartdns.xxx.core."));
+		o.rmempty = false;
+		o.default = o.disabled;
+		// Upstream servers;
+		s = m.section(form.GridSection, "server", _("Upstream Servers"),
+			_("Upstream Servers, support UDP, TCP protocol. Please configure multiple DNS servers, "
+				+ "including multiple foreign DNS servers."));
+		s.anonymous = true;
+		s.addremove = true;
+
+		s.tab('general', _('General Settings'));
+		s.tab('advanced', _('Advanced Settings'));
+
+		// enable flag;
+		o = s.taboption("general", form.Flag, "enabled", _("Enable"), _("Enable"));
+		o.rmempty = false;
+		o.default = o.enabled;
+		o.editable = true;
+
+		// name;
+		o = s.taboption("general", form.Value, "name", _("DNS Server Name"), _("DNS Server Name"));
+
+		// IP address;
+		o = s.taboption("general", form.Value, "ip", _("ip"), _("DNS Server ip"));
+		o.datatype = "or(ipaddr, string)";
+		o.rmempty = false;
+
+		// port;
+		o = s.taboption("general", form.Value, "port", _("port"), _("DNS Server port"));
+		o.placeholder = "default";
+		o.datatype = "port";
+		o.rempty = true;
+		o.depends("type", "udp");
+		o.depends("type", "tcp");
+		o.depends("type", "tls");
+
+		// type;
+		o = s.taboption("general", form.ListValue, "type", _("type"), _("DNS Server type"));
+		o.placeholder = "udp";
+		o.value("udp", _("udp"));
+		o.value("tcp", _("tcp"));
+		o.value("tls", _("tls"));
+		o.value("https", _("https"));
+		o.default = "udp";
+		o.rempty = false;
+
+		// Advanced Options
+		// server group
+		o = s.taboption("advanced", form.Value, "server_group", _("Server Group"), _("DNS Server group belongs to, "
+			+ "used with nameserver, such as office, home."))
+		o.rmempty = true
+		o.placeholder = "default"
+		o.datatype = "hostname"
+		o.rempty = true
+		o.modalonly = true;
+
+		// blacklist_ip
+		o = s.taboption("advanced", form.Flag, "blacklist_ip", _("IP Blacklist Filtering"),
+			_("Filtering IP with blacklist"))
+		o.rmempty = false
+		o.default = o.disabled
+		o.modalonly = true;
+
+		// TLS host verify
+		o = s.taboption("advanced", form.Value, "tls_host_verify", _("TLS Hostname Verify"),
+			_("Set TLS hostname to verify."))
+		o.default = ""
+		o.datatype = "string"
+		o.rempty = true
+		o.modalonly = true;
+		o.depends("type", "tls")
+		o.depends("type", "https")
+
+		// SNI host name
+		o = s.taboption("advanced", form.Value, "host_name", _("TLS SNI name"),
+			_("Sets the server name indication for query."))
+		o.default = ""
+		o.datatype = "hostname"
+		o.rempty = true
+		o.modalonly = true;
+		o.depends("type", "tls")
+		o.depends("type", "https")
+
+		// http host
+		o = s.taboption("advanced", form.Value, "http_host", _("HTTP Host"),
+			_("Set the HTTP host used for the query. Use this parameter when the host of the URL address is an IP address."))
+		o.default = ""
+		o.datatype = "hostname"
+		o.rempty = true
+		o.modalonly = true;
+		o.depends("type", "https")
+
+		// SPKI pin
+		o = s.taboption("advanced", form.Value, "spki_pin", _("TLS SPKI Pinning"),
+			_("Used to verify the validity of the TLS server, The value is Base64 encoded SPKI fingerprint, "
+				+ "leaving blank to indicate that the validity of TLS is not verified."))
+		o.default = ""
+		o.datatype = "string"
+		o.rempty = true
+		o.modalonly = true;
+		o.depends("type", "tls")
+		o.depends("type", "https")
+
+		// other args
+		o = s.taboption("advanced", form.Value, "addition_arg", _("Additional Server Args"),
+			_("Additional Args for upstream dns servers"))
+		o.default = ""
+		o.rempty = true
+		o.modalonly = true;
+
+		// Doman addresss;
+		s = m.section(form.TypedSection, "smartdns", _("Advanced Settings"), _("Advanced Settings"));
+		s.anonymous = true;
+
+		s.tab("domain-address", _("Domain Address"), _("Set Specific domain ip address."));
+		s.tab("blackip-list", _("IP Blacklist"), _("Set Specific ip blacklist."));
+
+		o = s.taboption("domain-address", form.TextValue, "address_conf",
+			_(""),
+			_("Specify an IP address to return for any host in the given domains, Queries in the domains are never "
+				+ "forwarded and always replied to with the specified IP address which may be IPv4 or IPv6."));
+		o.rows = 20;
+		o.cfgvalue = function (section_id) {
+			return fs.trimmed('/etc/smartdns/address.conf');
+		};
+		o.write = function (section_id, formvalue) {
+			return fs.write('/etc/smartdns/address.conf', formvalue.trim().replace(/\r\n/g, '\n') + '\n');
+		};
+
+		// IP Blacklist;
+		// blacklist;
+		o = s.taboption("blackip-list", form.TextValue, "blackip_ip_conf",
+			_(""),
+			_("Configure IP blacklists that will be filtered from the results of specific DNS server."));
+		o.rows = 20;
+		o.cfgvalue = function (section_id) {
+			return fs.trimmed('/etc/smartdns/blacklist-ip.conf');
+		};
+		o.write = function (section_id, formvalue) {
+			return fs.write('/etc/smartdns/blacklist-ip.conf', formvalue.trim().replace(/\r\n/g, '\n') + '\n');
+		};
+
+		// Doman addresss;
+		s = m.section(form.TypedSection, "smartdns", _("Technical Support"),
+			_("If you like this software, please buy me a cup of coffee."));
+		s.anonymous = true;
+
+		o = s.option(form.Button, "web");
+		o.title = _("SmartDNS official website");
+		o.inputtitle = _("open website");
+		o.inputstyle = "apply";
+		o.onclick = function () {
+			window.open("https://pymumu.github.io/smartdns", '_blank');
+		};
+
+		o = s.option(form.Button, "Donate");
+		o.title = _("Donate to smartdns");
+		o.inputtitle = _("Donate");
+		o.inputstyle = "apply";
+		o.onclick = function () {
+			window.open("https://pymumu.github.io/smartdns/#donate", '_blank');
+		};
+
+		return m.render();
+	}
+});
+

+ 297 - 0
package/luci/files/luci/i18n/smartdns.zh-cn.po

@@ -0,0 +1,297 @@
+msgid ""
+msgstr "Content-Type: text/plain; charset=UTF-8\n"
+
+msgid "SmartDNS"
+msgstr "SmartDNS"
+
+msgid "SmartDNS is a local high-performance DNS server"
+msgstr "SmartDNS是一个本地高性能DNS服务器"
+
+msgid "SmartDNS Server"
+msgstr "SmartDNS 服务器"
+
+msgid "SmartDNS is a local high-performance DNS server, supports finding fastest IP, supports ad filtering, and supports avoiding DNS poisoning."
+msgstr "SmartDNS是一个本地高性能DNS服务器,支持返回最快IP,支持广告过滤。"
+
+msgid "Custom Settings"
+msgstr "自定义设置"
+
+msgid "General Settings"
+msgstr "基本设置"
+
+msgid "Settings"
+msgstr "设置"
+
+msgid "Advanced Settings"
+msgstr "高级设置"
+
+msgid "RUNNING"
+msgstr "运行中"
+
+msgid "NOT RUNNING"
+msgstr "未运行"
+
+msgid "Generate Coredump"
+msgstr "生成coredump"
+
+msgid "Generate Coredump file when smartdns crash, coredump file is located at /tmp/smartdns.xxx.core."
+msgstr "当smartdns异常时生成coredump文件,coredump文件在/tmp/smartdns.xxx.core."
+
+msgid "Server Name"
+msgstr "服务器名称"
+
+msgid "Smartdns server name"
+msgstr "SmartDNS的服务器名称,默认为smartdns,留空为主机名"
+
+msgid "SmartDNS is a local dns server to find fastest ip."
+msgstr "本地高性能服务器,优化网络访问性能。"
+
+msgid "Enable or disable smartdns server"
+msgstr "启用或禁用SmartDNS服务"
+
+msgid "Local Port"
+msgstr "本地端口"
+
+msgid "Smartdns local server port"
+msgstr "SmartDNS本地服务端口"
+
+msgid "IPV4 53 Port Redirect Failure"
+msgstr "IPV4 53端口重定向失败"
+
+msgid "IPV6 53 Port Redirect Failure"
+msgstr "IPV6 53端口重定向失败"
+
+msgid "Dnsmasq Forwared To Smartdns Failure"
+msgstr "重定向dnsmasq到smartdns失败"
+
+msgid "TCP Server"
+msgstr "TCP服务器"
+
+msgid "Enable TCP DNS Server"
+msgstr "启用TCP服务器"
+
+msgid "IPV6 Server"
+msgstr "IPV6服务器"
+
+msgid "Enable IPV6 DNS Server"
+msgstr "启用IPV6服务器"
+
+msgid "Dual-stack IP Selection"
+msgstr "双栈IP优选"
+
+msgid "Enable IP selection between IPV4 and IPV6"
+msgstr "启用或禁用IPV4,IPV6间的IP优选策略。"
+
+msgid "Domain prefetch"
+msgstr "域名预加载"
+
+msgid "Enable domain prefetch, accelerate domain response speed."
+msgstr "启用域名预加载,加速域名响应速度。"
+
+msgid "Redirect"
+msgstr "重定向"
+
+msgid "SmartDNS redirect mode"
+msgstr "SmartDNS 重定向模式"
+
+msgid "Run as dnsmasq upstream server"
+msgstr "作为dnsmasq的上游服务器"
+
+msgid "Redirect 53 port to SmartDNS"
+msgstr "重定向53端口到SmartDNS"
+
+msgid "Cache Size"
+msgstr "缓存大小"
+
+msgid "DNS domain result cache size"
+msgstr "缓存DNS的结果,缓存大小,配置零则不缓存"
+
+msgid "Domain TTL"
+msgstr "域名TTL"
+
+msgid "TTL for all domain result."
+msgstr "设置所有域名的TTL值"
+
+msgid "Domain TTL Min"
+msgstr "域名TTL最小值"
+
+msgid "Minimum TTL for all domain result."
+msgstr "设置所有域名的TTL最小值"
+
+msgid "Domain TTL Max"
+msgstr "域名TTL最大值"
+
+msgid "Maximum TTL for all domain result."
+msgstr "设置所有域名的TTL最大值"
+
+msgid "smartdns custom settings"
+msgstr "smartdns 自定义设置,具体配置参数参考指导"
+
+msgid "Second Server Settings"
+msgstr "第二DNS服务器"
+
+msgid "Enable or disable second DNS server."
+msgstr "是否启用第二DNS服务器。"
+
+msgid "Skip Speed Check"
+msgstr "跳过测速"
+
+msgid "Do not check speed."
+msgstr "禁用测速。"
+
+msgid "Server Group"
+msgstr "服务器组"
+
+msgid "Query DNS through specific dns server group, such as office, home."
+msgstr "使用指定服务器组查询,比如office, home。"
+
+msgid "Skip Address Rules"
+msgstr "跳过address规则"
+
+msgid "Skip address rules."
+msgstr "跳过address规则。"
+
+msgid "Skip Nameserver Rule"
+msgstr "跳过Nameserver规则"
+
+msgid "Skip nameserver rules."
+msgstr "跳过Nameserver规则。"
+
+msgid "Skip Ipset Rule"
+msgstr "跳过ipset规则"
+
+msgid "Skip ipset rules."
+msgstr "跳过ipset规则。"
+
+msgid "Skip SOA Address Rule"
+msgstr "跳过address SOA(#)规则"
+
+msgid "Skip SOA address rules."
+msgstr "跳过address SOA(#)规则。"
+
+msgid "Skip Dualstack Selection"
+msgstr "跳过双栈优选"
+
+msgid "Skip Sualstack Selection."
+msgstr "跳过双栈优选。"
+
+msgid "Skip Cache"
+msgstr "跳过cache"
+
+msgid "Skip Cache."
+msgstr "跳过cache。"
+
+msgid "Upstream Servers"
+msgstr "上游服务器"
+
+msgid "Upstream Servers, support UDP, TCP protocol. Please configure multiple DNS servers, including multiple foreign DNS servers."
+msgstr "上游DNS服务器列表,支持UDP,TCP协议,请配置多个上游DNS服务器,包括多个国内外服务器"
+
+msgid "DNS Server Name"
+msgstr "DNS服务器名称"
+
+msgid "port"
+msgstr "端口"
+
+msgid "DNS Server port"
+msgstr "DNS服务器端口"
+
+msgid "DNS Server ip"
+msgstr "DNS服务器IP"
+
+msgid "type"
+msgstr "类型"
+
+msgid "DNS Server type"
+msgstr "协议类型"
+
+msgid "Domain Address"
+msgstr "域名地址"
+
+msgid "TLS Hostname Verify"
+msgstr "校验TLS主机名"
+
+msgid "Set TLS hostname to verify."
+msgstr "设置校验TLS主机名。"
+
+msgid "TLS SNI name"
+msgstr "TLS SNI名称"
+
+msgid "HTTP Host"
+msgstr "HTTP主机"
+
+msgid "Sets the server name indication for query."
+msgstr "设置查询时使用的服务器SNI名称。"
+
+msgid "Set the HTTP host used for the query. Use this parameter when the host of the URL address is an IP address."
+msgstr "设置查询时使用的HTTP主机,当URL地址的host是IP地址时,使用此参数。"
+
+msgid "Server Group"
+msgstr "服务器组"
+
+msgid "DNS Server group belongs to, used with nameserver, such as office, home."
+msgstr "DNS服务器所属组, 配合nameserver使用,例如:office,home。"
+
+msgid "IP Blacklist Filtering"
+msgstr "IP黑名单过滤"
+
+msgid "Anti Answer Forgery"
+msgstr "反回答伪造"
+
+msgid "Anti answer forgery, if DNS does not work properly after enabling, please turn off this feature"
+msgstr "反回答伪造,如果启用后DNS工作不正常,请关闭此功能。"
+
+msgid "Filtering IP with blacklist"
+msgstr "使用IP黑名单过滤"
+
+msgid "TLS SPKI Pinning"
+msgstr "TLS SPKI 指纹"
+
+msgid "Used to verify the validity of the TLS server, The value is Base64 encoded SPKI fingerprint, leaving blank to indicate that the validity of TLS is not verified."
+msgstr "用于校验TLS服务器的有效性,数值为Base64编码的SPKI指纹, 留空表示不验证TLS的合法性"
+
+msgid "Additional Server Args"
+msgstr "额外的服务器参数"
+
+msgid "Additional Args for upstream dns servers"
+msgstr "额外的上游DNS服务器参数"
+
+msgid "Upstream DNS Server Configuration"
+msgstr "上游DNS服务器配置"
+
+msgid "Set Specific domain ip address."
+msgstr "指定特定域名的IP地址"
+
+msgid "Specify an IP address to return for any host in the given domains, Queries in the domains are never forwarded and always replied to with the specified IP address which may be IPv4 or IPv6."
+msgstr "配置特定域名返回特定的IP地址,域名查询将不到上游服务器请求,直接返回配置的IP地址,可用于广告屏蔽。"
+
+msgid "IP Blacklist"
+msgstr "IP黑名单"
+
+msgid "Set Specific ip blacklist."
+msgstr "设置IP黑名单列表"
+
+msgid "Configure IP blacklists that will be filtered from the results of specific DNS server."
+msgstr "配置需要从指定域名服务器结果过滤的IP黑名单。"
+
+msgid "Technical Support"
+msgstr "技术支持"
+
+msgid "If you like this software, please buy me a cup of coffee."
+msgstr "如果本软件对你有帮助,请给作者加个蛋。"
+
+msgid "SmartDNS official website"
+msgstr "SmartDNS官方网站"
+
+msgid "open website"
+msgstr "打开网站"
+
+msgid "Donate to smartdns"
+msgstr "捐助smartdns项目"
+
+msgid "Donate"
+msgstr "捐助"
+
+
+
+

+ 21 - 0
package/luci/files/usr/share/rpcd/acl.d/luci-app-smartdns.json

@@ -0,0 +1,21 @@
+{
+	"luci-app-smartdns": {
+		"description": "Grant access to LuCI app smartdns",
+		"read": {
+			"file": {
+				"/etc/smartdns/*": [ "read" ],
+				"/usr/sbin/iptables": [ "exec" ],
+				"/usr/sbin/ip6tables": [ "exec" ],
+				"/usr/sbin/smartdns": [ "exec" ]
+			},
+			"ubus": {
+				"service": [ "list" ]
+			}
+		},
+		"write": {
+			"file": {
+				"/etc/smartdns/*": [ "write" ]
+			}
+		}
+	}
+}

+ 144 - 0
package/luci/make.sh

@@ -0,0 +1,144 @@
+#!/bin/sh
+#
+# Copyright (C) 2018-2020 Ruilin Peng (Nick) <[email protected]>.
+#
+# smartdns is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# smartdns is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+CURR_DIR=$(cd $(dirname $0);pwd)
+
+VER="`date +"1.%Y.%m.%d-%H%M"`"
+SMARTDNS_DIR=$CURR_DIR/../../
+PO2LMO=
+
+showhelp()
+{
+	echo "Usage: make [OPTION]"
+	echo "Options:"
+	echo " -o               output directory."
+	echo " --arch           archtecture."
+	echo " --ver            version."
+	echo " -h               show this message."
+}
+
+build_tool()
+{
+	make -C $ROOT/tool/po2lmo -j 
+	PO2LMO="$ROOT/tool/po2lmo/src/po2lmo"
+
+}
+
+clean_tool()
+{
+	make -C $ROOT/tool/po2lmo clean
+}
+
+build()
+{
+
+	ROOT=/tmp/luci-app-smartdns
+	rm -fr $ROOT
+
+	mkdir -p $ROOT
+	cp $CURR_DIR/* $ROOT/ -af
+	cd $ROOT/
+	build_tool
+	
+	mkdir $ROOT/root/usr/lib/lua/ -p
+	mkdir $ROOT/root/usr/lib/lua/luci/controller/ -p
+	mkdir $ROOT/root/usr/share/rpcd/acl.d/ -p
+	cp $ROOT/files/luci/controller/* $ROOT/root/usr/lib/lua/luci/controller/ -avf
+	cp $ROOT/files/luci/i18n $ROOT/root/usr/lib/lua/luci/ -avf
+	cp $ROOT/files/luci/view $ROOT/root/usr/lib/lua/luci/ -avf
+
+	mkdir $ROOT/root/www/luci-static/resources/view -p
+	cp $ROOT/files/luci/htdocs/luci-static/resources/view/* $ROOT/root/www/luci-static/resources/view/ -avf
+
+	#Generate Language
+	$PO2LMO $ROOT/files/luci/i18n/smartdns.zh-cn.po $ROOT/root/usr/lib/lua/luci/i18n/smartdns.zh-cn.lmo
+	rm $ROOT/root/usr/lib/lua/luci/i18n/smartdns.zh-cn.po
+
+	cp $ROOT/files/etc $ROOT/root/ -avf
+	cp $ROOT/files/usr $ROOT/root/ -avf
+	INST_SIZE="`du -sb $ROOT/root/ | awk '{print $1}'`"
+	
+	sed -i "s/^Architecture.*/Architecture: all/g" $ROOT/control/control
+	sed -i "s/Version:.*/Version: $VER/" $ROOT/control/control
+
+	if [ ! -z "$INST_SIZE" ]; then
+		echo "Installed-Size: $INST_SIZE" >> $ROOT/control/control
+	fi
+
+	cd $ROOT/control
+	chmod +x *
+	tar zcf ../control.tar.gz ./
+	cd $ROOT
+
+	tar zcf $ROOT/data.tar.gz -C root .
+	tar zcf $OUTPUTDIR/luci-app-smartdns.$VER.$FILEARCH.ipk control.tar.gz data.tar.gz debian-binary
+
+	rm -fr $ROOT/
+}
+
+main()
+{
+	OPTS=`getopt -o o:h --long arch:,ver:,filearch: \
+		-n  "" -- "$@"`
+
+	if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi
+
+	# Note the quotes around `$TEMP': they are essential!
+	eval set -- "$OPTS"
+
+	while true; do
+		case "$1" in
+		--arch)
+			ARCH="$2"
+			shift 2;;
+		--filearch)
+			FILEARCH="$2"
+			shift 2;;
+		--ver)
+			VER="$2"
+			shift 2;;
+		-o )
+			OUTPUTDIR="$2"
+			shift 2;;
+		-h | --help )
+			showhelp
+			return 0
+			shift ;;
+		-- ) shift; break ;;
+		* ) break ;;
+		esac
+	done
+
+	if [ -z "$ARCH" ]; then
+		echo "please input arch."
+		return 1;
+	fi
+
+	if [ -z "$FILEARCH" ]; then
+		FILEARCH=$ARCH
+	fi
+
+	if [ -z "$OUTPUTDIR" ]; then
+		OUTPUTDIR=$CURR_DIR;
+	fi
+
+	build
+}
+
+main $@
+exit $?
+
+

+ 12 - 0
package/luci/tool/po2lmo/Makefile

@@ -0,0 +1,12 @@
+
+INSTALL = install
+PREFIX  = /usr/bin
+
+po2lmo: src/po2lmo.o src/template_lmo.o
+	$(CC) $(LDFLAGS) -o src/po2lmo src/po2lmo.o src/template_lmo.o
+
+install:
+	$(INSTALL) -m 755 src/po2lmo $(PREFIX)
+
+clean:
+	$(RM) src/po2lmo src/*.o

+ 247 - 0
package/luci/tool/po2lmo/src/po2lmo.c

@@ -0,0 +1,247 @@
+/*
+ * lmo - Lua Machine Objects - PO to LMO conversion tool
+ *
+ *   Copyright (C) 2009-2012 Jo-Philipp Wich <[email protected]>
+ *
+ *  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.
+ */
+
+#include "template_lmo.h"
+
+static void die(const char *msg)
+{
+	fprintf(stderr, "Error: %s\n", msg);
+	exit(1);
+}
+
+static void usage(const char *name)
+{
+	fprintf(stderr, "Usage: %s input.po output.lmo\n", name);
+	exit(1);
+}
+
+static void print(const void *ptr, size_t size, size_t nmemb, FILE *stream)
+{
+	if( fwrite(ptr, size, nmemb, stream) == 0 )
+		die("Failed to write stdout");
+}
+
+static int extract_string(const char *src, char *dest, int len)
+{
+	int pos = 0;
+	int esc = 0;
+	int off = -1;
+
+	for( pos = 0; (pos < strlen(src)) && (pos < len); pos++ )
+	{
+		if( (off == -1) && (src[pos] == '"') )
+		{
+			off = pos + 1;
+		}
+		else if( off >= 0 )
+		{
+			if( esc == 1 )
+			{
+				switch (src[pos])
+				{
+				case '"':
+				case '\\':
+					off++;
+					break;
+				}
+				dest[pos-off] = src[pos];
+				esc = 0;
+			}
+			else if( src[pos] == '\\' )
+			{
+				dest[pos-off] = src[pos];
+				esc = 1;
+			}
+			else if( src[pos] != '"' )
+			{
+				dest[pos-off] = src[pos];
+			}
+			else
+			{
+				dest[pos-off] = '\0';
+				break;
+			}
+		}
+	}
+
+	return (off > -1) ? strlen(dest) : -1;
+}
+
+static int cmp_index(const void *a, const void *b)
+{
+	uint32_t x = ((const lmo_entry_t *)a)->key_id;
+	uint32_t y = ((const lmo_entry_t *)b)->key_id;
+
+	if (x < y)
+		return -1;
+	else if (x > y)
+		return 1;
+
+	return 0;
+}
+
+static void print_uint32(uint32_t x, FILE *out)
+{
+	uint32_t y = htonl(x);
+	print(&y, sizeof(uint32_t), 1, out);
+}
+
+static void print_index(void *array, int n, FILE *out)
+{
+	lmo_entry_t *e;
+
+	qsort(array, n, sizeof(*e), cmp_index);
+
+	for (e = array; n > 0; n--, e++)
+	{
+		print_uint32(e->key_id, out);
+		print_uint32(e->val_id, out);
+		print_uint32(e->offset, out);
+		print_uint32(e->length, out);
+	}
+}
+
+int main(int argc, char *argv[])
+{
+	char line[4096];
+	char key[4096];
+	char val[4096];
+	char tmp[4096];
+	int state  = 0;
+	int offset = 0;
+	int length = 0;
+	int n_entries = 0;
+	void *array = NULL;
+	lmo_entry_t *entry = NULL;
+	uint32_t key_id, val_id;
+
+	FILE *in;
+	FILE *out;
+
+	if( (argc != 3) || ((in = fopen(argv[1], "r")) == NULL) || ((out = fopen(argv[2], "w")) == NULL) )
+		usage(argv[0]);
+
+	memset(line, 0, sizeof(key));
+	memset(key, 0, sizeof(val));
+	memset(val, 0, sizeof(val));
+
+	while( (NULL != fgets(line, sizeof(line), in)) || (state >= 2 && feof(in)) )
+	{
+		if( state == 0 && strstr(line, "msgid \"") == line )
+		{
+			switch(extract_string(line, key, sizeof(key)))
+			{
+				case -1:
+					die("Syntax error in msgid");
+				case 0:
+					state = 1;
+					break;
+				default:
+					state = 2;
+			}
+		}
+		else if( state == 1 || state == 2 )
+		{
+			if( strstr(line, "msgstr \"") == line || state == 2 )
+			{
+				switch(extract_string(line, val, sizeof(val)))
+				{
+					case -1:
+						state = 4;
+						break;
+					default:
+						state = 3;
+				}
+			}
+			else
+			{
+				switch(extract_string(line, tmp, sizeof(tmp)))
+				{
+					case -1:
+						state = 2;
+						break;
+					default:
+						strcat(key, tmp);
+				}
+			}
+		}
+		else if( state == 3 )
+		{
+			switch(extract_string(line, tmp, sizeof(tmp)))
+			{
+				case -1:
+					state = 4;
+					break;
+				default:
+					strcat(val, tmp);
+			}
+		}
+
+		if( state == 4 )
+		{
+			if( strlen(key) > 0 && strlen(val) > 0 )
+			{
+				key_id = sfh_hash(key, strlen(key));
+				val_id = sfh_hash(val, strlen(val));
+
+				if( key_id != val_id )
+				{
+					n_entries++;
+					array = realloc(array, n_entries * sizeof(lmo_entry_t));
+					entry = (lmo_entry_t *)array + n_entries - 1;
+
+					if (!array)
+						die("Out of memory");
+
+					entry->key_id = key_id;
+					entry->val_id = val_id;
+					entry->offset = offset;
+					entry->length = strlen(val);
+
+					length = strlen(val) + ((4 - (strlen(val) % 4)) % 4);
+
+					print(val, length, 1, out);
+					offset += length;
+				}
+			}
+
+			state = 0;
+			memset(key, 0, sizeof(key));
+			memset(val, 0, sizeof(val));
+		}
+
+		memset(line, 0, sizeof(line));
+	}
+
+	print_index(array, n_entries, out);
+
+	if( offset > 0 )
+	{
+		print_uint32(offset, out);
+		fsync(fileno(out));
+		fclose(out);
+	}
+	else
+	{
+		fclose(out);
+		unlink(argv[2]);
+	}
+
+	fclose(in);
+	return(0);
+}

+ 328 - 0
package/luci/tool/po2lmo/src/template_lmo.c

@@ -0,0 +1,328 @@
+/*
+ * lmo - Lua Machine Objects - Base functions
+ *
+ *   Copyright (C) 2009-2010 Jo-Philipp Wich <[email protected]>
+ *
+ *  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.
+ */
+
+#include "template_lmo.h"
+
+/*
+ * Hash function from http://www.azillionmonkeys.com/qed/hash.html
+ * Copyright (C) 2004-2008 by Paul Hsieh
+ */
+
+uint32_t sfh_hash(const char *data, int len)
+{
+	uint32_t hash = len, tmp;
+	int rem;
+
+	if (len <= 0 || data == NULL) return 0;
+
+	rem = len & 3;
+	len >>= 2;
+
+	/* Main loop */
+	for (;len > 0; len--) {
+		hash  += sfh_get16(data);
+		tmp    = (sfh_get16(data+2) << 11) ^ hash;
+		hash   = (hash << 16) ^ tmp;
+		data  += 2*sizeof(uint16_t);
+		hash  += hash >> 11;
+	}
+
+	/* Handle end cases */
+	switch (rem) {
+		case 3: hash += sfh_get16(data);
+			hash ^= hash << 16;
+			hash ^= data[sizeof(uint16_t)] << 18;
+			hash += hash >> 11;
+			break;
+		case 2: hash += sfh_get16(data);
+			hash ^= hash << 11;
+			hash += hash >> 17;
+			break;
+		case 1: hash += *data;
+			hash ^= hash << 10;
+			hash += hash >> 1;
+	}
+
+	/* Force "avalanching" of final 127 bits */
+	hash ^= hash << 3;
+	hash += hash >> 5;
+	hash ^= hash << 4;
+	hash += hash >> 17;
+	hash ^= hash << 25;
+	hash += hash >> 6;
+
+	return hash;
+}
+
+uint32_t lmo_canon_hash(const char *str, int len)
+{
+	char res[4096];
+	char *ptr, prev;
+	int off;
+
+	if (!str || len >= sizeof(res))
+		return 0;
+
+	for (prev = ' ', ptr = res, off = 0; off < len; prev = *str, off++, str++)
+	{
+		if (isspace(*str))
+		{
+			if (!isspace(prev))
+				*ptr++ = ' ';
+		}
+		else
+		{
+			*ptr++ = *str;
+		}
+	}
+
+	if ((ptr > res) && isspace(*(ptr-1)))
+		ptr--;
+
+	return sfh_hash(res, ptr - res);
+}
+
+lmo_archive_t * lmo_open(const char *file)
+{
+	int in = -1;
+	uint32_t idx_offset = 0;
+	struct stat s;
+
+	lmo_archive_t *ar = NULL;
+
+	if (stat(file, &s) == -1)
+		goto err;
+
+	if ((in = open(file, O_RDONLY)) == -1)
+		goto err;
+
+	if ((ar = (lmo_archive_t *)malloc(sizeof(*ar))) != NULL)
+	{
+		memset(ar, 0, sizeof(*ar));
+
+		ar->fd     = in;
+		ar->size = s.st_size;
+
+		fcntl(ar->fd, F_SETFD, fcntl(ar->fd, F_GETFD) | FD_CLOEXEC);
+
+		if ((ar->mmap = mmap(NULL, ar->size, PROT_READ, MAP_SHARED, ar->fd, 0)) == MAP_FAILED)
+			goto err;
+
+		idx_offset = ntohl(*((const uint32_t *)
+		                     (ar->mmap + ar->size - sizeof(uint32_t))));
+
+		if (idx_offset >= ar->size)
+			goto err;
+
+		ar->index  = (lmo_entry_t *)(ar->mmap + idx_offset);
+		ar->length = (ar->size - idx_offset - sizeof(uint32_t)) / sizeof(lmo_entry_t);
+		ar->end    = ar->mmap + ar->size;
+
+		return ar;
+	}
+
+err:
+	if (in > -1)
+		close(in);
+
+	if (ar != NULL)
+	{
+		if ((ar->mmap != NULL) && (ar->mmap != MAP_FAILED))
+			munmap(ar->mmap, ar->size);
+
+		free(ar);
+	}
+
+	return NULL;
+}
+
+void lmo_close(lmo_archive_t *ar)
+{
+	if (ar != NULL)
+	{
+		if ((ar->mmap != NULL) && (ar->mmap != MAP_FAILED))
+			munmap(ar->mmap, ar->size);
+
+		close(ar->fd);
+		free(ar);
+
+		ar = NULL;
+	}
+}
+
+
+lmo_catalog_t *_lmo_catalogs = NULL;
+lmo_catalog_t *_lmo_active_catalog = NULL;
+
+int lmo_load_catalog(const char *lang, const char *dir)
+{
+	DIR *dh = NULL;
+	char pattern[16];
+	char path[PATH_MAX];
+	struct dirent *de = NULL;
+
+	lmo_archive_t *ar = NULL;
+	lmo_catalog_t *cat = NULL;
+
+	if (!lmo_change_catalog(lang))
+		return 0;
+
+	if (!dir || !(dh = opendir(dir)))
+		goto err;
+
+	if (!(cat = malloc(sizeof(*cat))))
+		goto err;
+
+	memset(cat, 0, sizeof(*cat));
+
+	snprintf(cat->lang, sizeof(cat->lang), "%s", lang);
+	snprintf(pattern, sizeof(pattern), "*.%s.lmo", lang);
+
+	while ((de = readdir(dh)) != NULL)
+	{
+		if (!fnmatch(pattern, de->d_name, 0))
+		{
+			snprintf(path, sizeof(path), "%s/%s", dir, de->d_name);
+			ar = lmo_open(path);
+
+			if (ar)
+			{
+				ar->next = cat->archives;
+				cat->archives = ar;
+			}
+		}
+	}
+
+	closedir(dh);
+
+	cat->next = _lmo_catalogs;
+	_lmo_catalogs = cat;
+
+	if (!_lmo_active_catalog)
+		_lmo_active_catalog = cat;
+
+	return 0;
+
+err:
+	if (dh) closedir(dh);
+	if (cat) free(cat);
+
+	return -1;
+}
+
+int lmo_change_catalog(const char *lang)
+{
+	lmo_catalog_t *cat;
+
+	for (cat = _lmo_catalogs; cat; cat = cat->next)
+	{
+		if (!strncmp(cat->lang, lang, sizeof(cat->lang)))
+		{
+			_lmo_active_catalog = cat;
+			return 0;
+		}
+	}
+
+	return -1;
+}
+
+static lmo_entry_t * lmo_find_entry(lmo_archive_t *ar, uint32_t hash)
+{
+	unsigned int m, l, r;
+	uint32_t k;
+
+	l = 0;
+	r = ar->length - 1;
+
+	while (1)
+	{
+		m = l + ((r - l) / 2);
+
+		if (r < l)
+			break;
+
+		k = ntohl(ar->index[m].key_id);
+
+		if (k == hash)
+			return &ar->index[m];
+
+		if (k > hash)
+		{
+			if (!m)
+				break;
+
+			r = m - 1;
+		}
+		else
+		{
+			l = m + 1;
+		}
+	}
+
+	return NULL;
+}
+
+int lmo_translate(const char *key, int keylen, char **out, int *outlen)
+{
+	uint32_t hash;
+	lmo_entry_t *e;
+	lmo_archive_t *ar;
+
+	if (!key || !_lmo_active_catalog)
+		return -2;
+
+	hash = lmo_canon_hash(key, keylen);
+
+	for (ar = _lmo_active_catalog->archives; ar; ar = ar->next)
+	{
+		if ((e = lmo_find_entry(ar, hash)) != NULL)
+		{
+			*out = ar->mmap + ntohl(e->offset);
+			*outlen = ntohl(e->length);
+			return 0;
+		}
+	}
+
+	return -1;
+}
+
+void lmo_close_catalog(const char *lang)
+{
+	lmo_archive_t *ar, *next;
+	lmo_catalog_t *cat, *prev;
+
+	for (prev = NULL, cat = _lmo_catalogs; cat; prev = cat, cat = cat->next)
+	{
+		if (!strncmp(cat->lang, lang, sizeof(cat->lang)))
+		{
+			if (prev)
+				prev->next = cat->next;
+			else
+				_lmo_catalogs = cat->next;
+
+			for (ar = cat->archives; ar; ar = next)
+			{
+				next = ar->next;
+				lmo_close(ar);
+			}
+
+			free(cat);
+			break;
+		}
+	}
+}

+ 92 - 0
package/luci/tool/po2lmo/src/template_lmo.h

@@ -0,0 +1,92 @@
+/*
+ * lmo - Lua Machine Objects - General header
+ *
+ *   Copyright (C) 2009-2012 Jo-Philipp Wich <[email protected]>
+ *
+ *  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.
+ */
+
+#ifndef _TEMPLATE_LMO_H_
+#define _TEMPLATE_LMO_H_
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fnmatch.h>
+#include <dirent.h>
+#include <ctype.h>
+#include <limits.h>
+
+#if (defined(__GNUC__) && defined(__i386__))
+#define sfh_get16(d) (*((const uint16_t *) (d)))
+#else
+#define sfh_get16(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8)\
+					   +(uint32_t)(((const uint8_t *)(d))[0]) )
+#endif
+
+
+struct lmo_entry {
+	uint32_t key_id;
+	uint32_t val_id;
+	uint32_t offset;
+	uint32_t length;
+} __attribute__((packed));
+
+typedef struct lmo_entry lmo_entry_t;
+
+
+struct lmo_archive {
+	int         fd;
+	int	        length;
+	uint32_t    size;
+	lmo_entry_t *index;
+	char        *mmap;
+	char		*end;
+	struct lmo_archive *next;
+};
+
+typedef struct lmo_archive lmo_archive_t;
+
+
+struct lmo_catalog {
+	char lang[6];
+	struct lmo_archive *archives;
+	struct lmo_catalog *next;
+};
+
+typedef struct lmo_catalog lmo_catalog_t;
+
+
+uint32_t sfh_hash(const char *data, int len);
+uint32_t lmo_canon_hash(const char *data, int len);
+
+lmo_archive_t * lmo_open(const char *file);
+void lmo_close(lmo_archive_t *ar);
+
+
+extern lmo_catalog_t *_lmo_catalogs;
+extern lmo_catalog_t *_lmo_active_catalog;
+
+int lmo_load_catalog(const char *lang, const char *dir);
+int lmo_change_catalog(const char *lang);
+int lmo_translate(const char *key, int keylen, char **out, int *outlen);
+void lmo_close_catalog(const char *lang);
+
+#endif