From 840a9ad63ff4a154f6765272f2733733ec4933a5 Mon Sep 17 00:00:00 2001 From: Jianhui Zhao Date: Thu, 26 Jul 2018 23:28:41 +0800 Subject: [PATCH] wifidog-ng: Update to 2.0.0 Signed-off-by: Jianhui Zhao --- net/wifidog-ng/Makefile | 34 ++- .../files/{wifidog-ng.crt => ssl.crt} | 0 .../files/{wifidog-ng.key => ssl.key} | 0 net/wifidog-ng/files/wifidog-ng.config | 13 +- net/wifidog-ng/files/wifidog-ng.init | 102 ++++++-- net/wifidog-ng/files/wifidog-ng.lua | 37 +++ net/wifidog-ng/files/wifidog-ng/auth.lua | 221 ++++++++++++++++++ net/wifidog-ng/files/wifidog-ng/config.lua | 158 +++++++++++++ net/wifidog-ng/files/wifidog-ng/heartbeat.lua | 46 ++++ net/wifidog-ng/files/wifidog-ng/ubus.lua | 128 ++++++++++ net/wifidog-ng/files/wifidog-ng/util.lua | 83 +++++++ net/wifidog-ng/files/wifidog-ng/version.lua | 30 +++ net/wifidog-ng/src/Makefile | 18 ++ net/wifidog-ng/src/config.c | 174 ++++++++++++++ net/wifidog-ng/src/config.h | 31 +++ net/wifidog-ng/src/main.c | 176 ++++++++++++++ net/wifidog-ng/src/utils.h | 60 +++++ 17 files changed, 1267 insertions(+), 44 deletions(-) rename net/wifidog-ng/files/{wifidog-ng.crt => ssl.crt} (100%) rename net/wifidog-ng/files/{wifidog-ng.key => ssl.key} (100%) create mode 100644 net/wifidog-ng/files/wifidog-ng.lua create mode 100644 net/wifidog-ng/files/wifidog-ng/auth.lua create mode 100644 net/wifidog-ng/files/wifidog-ng/config.lua create mode 100644 net/wifidog-ng/files/wifidog-ng/heartbeat.lua create mode 100644 net/wifidog-ng/files/wifidog-ng/ubus.lua create mode 100644 net/wifidog-ng/files/wifidog-ng/util.lua create mode 100644 net/wifidog-ng/files/wifidog-ng/version.lua create mode 100644 net/wifidog-ng/src/Makefile create mode 100644 net/wifidog-ng/src/config.c create mode 100644 net/wifidog-ng/src/config.h create mode 100644 net/wifidog-ng/src/main.c create mode 100644 net/wifidog-ng/src/utils.h diff --git a/net/wifidog-ng/Makefile b/net/wifidog-ng/Makefile index f7b6e4ee7..ecaf54044 100644 --- a/net/wifidog-ng/Makefile +++ b/net/wifidog-ng/Makefile @@ -1,5 +1,5 @@ -# -# Copyright (C) 2014-2017 OpenWrt.org + +# Copyright (C) 2018 Jianhui Zhao # # This is free software, licensed under the GNU General Public License v2. # See /LICENSE for more information. @@ -8,15 +8,10 @@ include $(TOPDIR)/rules.mk PKG_NAME:=wifidog-ng -PKG_VERSION:=1.5.6 +PKG_VERSION:=2.0.0 PKG_RELEASE:=1 -PKG_SOURCE_PROTO:=git -PKG_SOURCE_VERSION:=v$(PKG_VERSION) -PKG_SOURCE_URL=https://github.com/zhaojh329/wifidog-ng.git -PKG_MIRROR_HASH:=a81e9a4d5feb3facbe1b2b55d2d813944b569865f53421680efbfc6876aa3f5d - -PKG_BUILD_DIR=$(BUILD_DIR)/$(PKG_NAME)-$(BUILD_VARIANT)/$(PKG_SOURCE_SUBDIR) +PKG_BUILD_DIR=$(BUILD_DIR)/$(PKG_NAME)-$(BUILD_VARIANT) PKG_LICENSE:=LGPL-2.1 PKG_LICENSE_FILES:=LICENSE @@ -24,15 +19,14 @@ PKG_LICENSE_FILES:=LICENSE PKG_MAINTAINER:=Jianhui Zhao include $(INCLUDE_DIR)/package.mk -include $(INCLUDE_DIR)/cmake.mk define Package/wifidog-ng/default SUBMENU:=Captive Portals SECTION:=net CATEGORY:=Network - TITLE:=Next generation WifiDog - DEPENDS:=+kmod-wifidog-ng +libuci +libuclient +libblobmsg-json +libubus +libcares \ - +ipset +libpcap + TITLE:=Next generation WifiDog implemented in Lua + DEPENDS:=+kmod-wifidog-ng +libubox-lua +libuci-lua +libubus-lua \ + +ipset +dnsmasq-full +luasocket endef define Package/wifidog-ng-nossl @@ -68,13 +62,14 @@ endef define Package/wifidog-ng/default/install $(INSTALL_DIR) $(1)/usr/bin $(1)/etc/init.d $(1)/etc/config \ - $(1)/etc/wifidog-ng $(1)//etc/hotplug.d/dhcp - $(INSTALL_BIN) $(PKG_BUILD_DIR)/src/wifidog-ng $(1)/usr/bin + $(1)/etc/wifidog-ng $(1)//etc/hotplug.d/dhcp $(1)/usr/lib/lua + $(INSTALL_BIN) ./files//wifidog-ng.lua $(1)/usr/bin/wifidog-ng $(INSTALL_BIN) ./files/wifidog-ng.init $(1)/etc/init.d/wifidog-ng $(INSTALL_CONF) ./files/wifidog-ng.config $(1)/etc/config/wifidog-ng - $(INSTALL_CONF) ./files/wifidog-ng.key $(1)/etc/wifidog-ng - $(INSTALL_CONF) ./files/wifidog-ng.crt $(1)/etc/wifidog-ng + $(INSTALL_CONF) ./files/ssl.key $(1)/etc/wifidog-ng + $(INSTALL_CONF) ./files/ssl.crt $(1)/etc/wifidog-ng $(INSTALL_DATA) ./files/wifidog-ng.hotplug $(1)/etc/hotplug.d/dhcp/00-wifidog-ng + $(CP) ./files/wifidog-ng $(1)/usr/lib/lua endef Package/wifidog-ng-nossl/install = $(Package/wifidog-ng/default/install) @@ -88,14 +83,13 @@ define KernelPackage/wifidog-ng SUBMENU:=Other modules TITLE:=Kernel module for wifidog-ng DEPENDS:=+kmod-nf-nat +kmod-ipt-ipset - FILES:=$(PKG_BUILD_DIR)/kmod/wifidog-ng.ko + FILES:=$(PKG_BUILD_DIR)/wifidog-ng.ko endef include $(INCLUDE_DIR)/kernel-defaults.mk define Build/Compile - $(call Build/Compile/Default) - $(MAKE) $(KERNEL_MAKEOPTS) SUBDIRS="$(PKG_BUILD_DIR)"/kmod modules + $(MAKE) $(KERNEL_MAKEOPTS) SUBDIRS="$(PKG_BUILD_DIR)" modules endef $(eval $(call BuildPackage,wifidog-ng-nossl)) diff --git a/net/wifidog-ng/files/wifidog-ng.crt b/net/wifidog-ng/files/ssl.crt similarity index 100% rename from net/wifidog-ng/files/wifidog-ng.crt rename to net/wifidog-ng/files/ssl.crt diff --git a/net/wifidog-ng/files/wifidog-ng.key b/net/wifidog-ng/files/ssl.key similarity index 100% rename from net/wifidog-ng/files/wifidog-ng.key rename to net/wifidog-ng/files/ssl.key diff --git a/net/wifidog-ng/files/wifidog-ng.config b/net/wifidog-ng/files/wifidog-ng.config index b30024030..c4c15039c 100644 --- a/net/wifidog-ng/files/wifidog-ng.config +++ b/net/wifidog-ng/files/wifidog-ng.config @@ -1,13 +1,14 @@ config gateway option enabled 1 - option ifname 'br-lan' + option interface 'lan' option port 2060 + option dhcp_host_white 1 option ssl_port 8443 option checkinterval 30 option client_timeout 5 option temppass_time 30 -config authserver +config server option host 'authserver.com' option port 80 option ssl 0 @@ -16,10 +17,4 @@ config authserver option portal_path 'portal' option msg_path 'gw_message.php' option ping_path 'ping' - option auth_path 'auth' - -config popularserver - list server www.baidu.com - list server www.qq.com - -config whitelist + option auth_path 'auth' \ No newline at end of file diff --git a/net/wifidog-ng/files/wifidog-ng.init b/net/wifidog-ng/files/wifidog-ng.init index 145ee6dc5..f39373278 100644 --- a/net/wifidog-ng/files/wifidog-ng.init +++ b/net/wifidog-ng/files/wifidog-ng.init @@ -5,45 +5,117 @@ START=95 BIN=/usr/bin/wifidog-ng -parse_whitelist_mac() { +dhcp_host_white=1 + +start_wifidog() { + local cfg="$1" + local enabled interface + + uci_validate_section wifidog-ng gateway "${1}" \ + 'enabled:bool:0' \ + 'interface:uci("network", "@interface"):lan' \ + 'dhcp_host_white:bool:1' + + [ $? -ne 0 ] && { + echo "validation gateway failed" >&2 + exit 1 + } + + [ $enabled -eq 1 ] || exit 0 + + # timeout = 49 days + ipset -! create wifidog-ng-mac hash:mac timeout 4294967 + ipset -! create wifidog-ng-ip hash:ip + + modprobe wifidog-ng + echo "enabled=1" > /proc/wifidog-ng/config + + procd_open_instance + procd_set_param command $BIN + procd_set_param respawn + procd_close_instance +} + +parse_server() { local cfg="$1" - local mac + local host - uci_validate_section wifidog-ng whitelist "${1}" \ + config_get host $cfg host + validate_data ip4addr "$host" 2> /dev/null + if [ $? -eq 0 ]; + then + ipset add wifidog-ng-ip $host + else + echo "ipset=/$host/wifidog-ng-ip" >> /tmp/dnsmasq.d/wifidog-ng + fi +} + +parse_validated_user() { + local cfg="$1" + local mac ip + + uci_validate_section wifidog-ng validated_user "${1}" \ 'mac:macaddr' [ $? -ne 0 ] && { - echo "validation whitelist_mac failed" >&2 + echo "validation validated_user failed" >&2 exit 1 } + + [ -n "$mac" ] && ipset add wifidog-ng-mac $mac } -parse_whitelist_domain() { +parse_validated_domain() { local cfg="$1" local domain - uci_validate_section wifidog-ng whitelist "${1}" \ + uci_validate_section wifidog-ng validated_domain "${1}" \ 'domain:host' [ $? -ne 0 ] && { - echo "validation whitelist_domain failed" >&2 + echo "validation validated_domain failed" >&2 exit 1 } + + [ -n "$domain" ] && echo "ipset=/$domain/wifidog-ng-ip" >> /tmp/dnsmasq.d/wifidog-ng } -start_service() { - modprobe wifidog-ng +parse_dhcp_host() { + local cfg="$1" + local mac ip + + uci_validate_section dhcp host "${1}" \ + 'mac:macaddr' + + [ $? -ne 0 ] && { + echo "validation validated dhcp host failed" >&2 + exit 1 + } + + [ -n "$mac" ] && ipset add wifidog-ng-mac $mac +} +start_service() { config_load wifidog-ng - config_foreach parse_whitelist_mac whitelist_mac - config_foreach parse_whitelist_domain whitelist_domain + config_foreach start_wifidog gateway - procd_open_instance - procd_set_param command $BIN - procd_set_param respawn - procd_close_instance + echo -n > /tmp/dnsmasq.d/wifidog-ng + + config_foreach parse_server server + config_foreach parse_validated_user validated_user + config_foreach parse_validated_domain validated_domain + + [ $dhcp_host_white -eq 1 ] && { + config_load dhcp + config_foreach parse_dhcp_host host + } + + /etc/init.d/dnsmasq restart & } stop_service() { rmmod wifidog-ng + + ipset destroy wifidog-ng-mac + ipset destroy wifidog-ng-ip } diff --git a/net/wifidog-ng/files/wifidog-ng.lua b/net/wifidog-ng/files/wifidog-ng.lua new file mode 100644 index 000000000..c6a63d63b --- /dev/null +++ b/net/wifidog-ng/files/wifidog-ng.lua @@ -0,0 +1,37 @@ +#!/usr/bin/env lua + +--[[ + Copyright (C) 2018 Jianhui Zhao + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This program 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA + --]] + +local uloop = require "uloop" +local uh = require "uhttpd" +local auth = require "wifidog-ng.auth" +local ubus = require "wifidog-ng.ubus" +local version = require "wifidog-ng.version" +local heartbeat = require "wifidog-ng.heartbeat" + +uh.log(uh.LOG_INFO, "Version: " .. version.string()) + +uloop.init() + +ubus.init() +auth.init() +heartbeat.start() + +uloop.run() diff --git a/net/wifidog-ng/files/wifidog-ng/auth.lua b/net/wifidog-ng/files/wifidog-ng/auth.lua new file mode 100644 index 000000000..63666a487 --- /dev/null +++ b/net/wifidog-ng/files/wifidog-ng/auth.lua @@ -0,0 +1,221 @@ +--[[ + Copyright (C) 2018 Jianhui Zhao + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This program 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA + --]] + +local uh = require "uhttpd" +local http = require "socket.http" +local util = require "wifidog-ng.util" +local config = require "wifidog-ng.config" + +local M = {} + +local apple_host = { + ["captive.apple.com"] = true, + ["www.apple.com"] = true, +} + +local terms = {} + +local function is_authed_user(mac) + local r = os.execute("ipset test wifidog-ng-mac " .. mac .. " 2> /dev/null") + return r == 0 +end + +local function allow_user(mac, temppass) + if not temppass then + terms[mac].authed = true + os.execute("ipset add wifidog-ng-mac " .. mac) + else + local cfg = config.get() + os.execute("ipset add wifidog-ng-mac " .. mac .. " timeout " .. cfg.temppass_time) + end +end + +local function deny_user(mac) + os.execute("ipset del wifidog-ng-mac " .. mac) +end + +function M.get_terms() + local r = {} + for k, v in pairs(terms) do + if v.authed then + r[k] = {ip = v.ip} + end + end + + return r +end + +function M.new_term(ip, mac, token) + terms[mac] = {ip = ip, token = token} + if token then + terms[mac].authed = true + allow_user(mac) + end +end + +local function http_callback_auth(cl) + local cfg = config.get() + local token = cl:get_var("token") + local ip = cl:get_remote_addr() + local mac = util.arp_get(cfg.gw_ifname, ip) + + if not mac then + uh.log(uh.LOG_ERR, "Not found macaddr for " .. ip) + cl:send_error(401, "Unauthorized", "Not found your macaddr") + return uh.REQUEST_DONE + end + + if token and #token > 0 then + if cl:get_var("logout") then + local url = string.format("%s&stage=logout&ip=%s&mac=%s&token=%s", cfg.auth_url, ip, mac, token) + http.request(url) + deny_user(mac) + else + local url = string.format("%s&stage=login&ip=%s&mac=%s&token=%s", cfg.auth_url, ip, mac, token) + local r = http.request(url) + + if not r then + cl:send_error(401, "Unauthorized") + return uh.REQUEST_DONE + end + + local auth = r:match("Auth: (%d)") + if auth == "1" then + allow_user(mac) + cl:redirect(302, string.format("%s&mac=%s", cfg.portal_url, mac)) + else + cl:redirect(302, string.format("%s&mac=%s", cfg.msg_url, mac)) + return uh.REQUEST_DONE + end + end + else + cl:send_error(401, "Unauthorized") + return uh.REQUEST_DONE + end +end + +local function http_callback_temppass(cl) + local cfg = config.get() + local ip = cl:get_remote_addr() + local mac = util.arp_get(cfg.gw_ifname, ip) + + if not mac then + uh.log(uh.LOG_ERR, "Not found macaddr for " .. ip) + cl:send_error(401, "Unauthorized", "Not found your macaddr") + return uh.REQUEST_DONE + end + + local script = cl:get_var("script") or "" + + cl:send_header(200, "OK", -1) + cl:header_end() + allow_user(mac, true) + cl:chunk_send(cl:get_var("script") or ""); + cl:request_done() + + return uh.REQUEST_DONE +end + +local function http_callback_404(cl, path) + local cfg = config.get() + + if cl:get_http_method() ~= uh.HTTP_METHOD_GET then + cl:send_error(401, "Unauthorized") + return uh.REQUEST_DONE + end + + local ip = cl:get_remote_addr() + local mac = util.arp_get(cfg.gw_ifname, ip) + if not mac then + uh.log(uh.LOG_ERR, "Not found macaddr for " .. ip) + cl:send_error(401, "Unauthorized", "Not found your macaddr") + return uh.REQUEST_DONE + end + + term = terms[mac] + if not term then + terms[mac] = {ip = ip} + end + + term = terms[mac] + + if is_authed_user(mac) then + cl:redirect(302, "%s&mac=%s", cfg.portal_url, mac) + return uh.REQUEST_DONE + end + + cl:send_header(200, "OK", -1) + cl:header_end() + + local header_host = cl:get_header("host") + if apple_host[header_host] then + local http_ver = cl:get_http_version() + if http_ver == uh.HTTP_VER_10 then + if not term.apple then + cl:chunk_send("fuck you") + term.apple = true + cl:request_done() + return uh.REQUEST_DONE + end + end + end + + local redirect_html = [[ + Success + + + Success + ]] + + cl:chunk_send(string.format(redirect_html, cfg.login_url, ip, mac)) + cl:request_done() + + return uh.REQUEST_DONE +end + +local function on_request(cl, path) + if path == "/wifidog/auth" then + return http_callback_auth(cl) + elseif path == "/wifidog/temppass" then + return http_callback_temppass(cl) + end + + return uh.REQUEST_CONTINUE +end + +function M.init() + local cfg = config.get() + + local srv = uh.new(cfg.gw_address, cfg.gw_port) + + srv:on_request(on_request) + srv:on_error404(http_callback_404) + + if uh.SSL_SUPPORTED then + local srv_ssl = uh.new(cfg.gw_address, cfg.gw_ssl_port) + + srv_ssl:ssl_init("/etc/wifidog-ng/ssl.crt", "/etc/wifidog-ng/ssl.key") + + srv_ssl:on_request(on_request) + srv_ssl:on_error404(http_callback_404) + end +end + +return M diff --git a/net/wifidog-ng/files/wifidog-ng/config.lua b/net/wifidog-ng/files/wifidog-ng/config.lua new file mode 100644 index 000000000..04f3d00f8 --- /dev/null +++ b/net/wifidog-ng/files/wifidog-ng/config.lua @@ -0,0 +1,158 @@ +--[[ + Copyright (C) 2018 Jianhui Zhao + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This program 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA + --]] + +local uci = require "uci" +local util = require "wifidog-ng.util" + +local M = {} + +local cfg = {} + +function M.parse() + local c = uci.cursor() + + c:foreach('wifidog-ng', 'gateway', function(s) + local port = s.port or 2060 + local ssl_port = s.ssl_port or 8443 + local interface = s.interface or "lan" + local checkinterval = s.checkinterval or 30 + local client_timeout = s.client_timeout or 5 + local temppass_time = s.temppass_time or 30 + local id = s.id + local address = s.address + + cfg.gw_port = tonumber(port) + cfg.gw_ssl_port = tonumber(ssl_port) + cfg.checkinterval = tonumber(checkinterval) + cfg.client_timeout = tonumber(client_timeout) + cfg.temppass_time = tonumber(temppass_time) + cfg.gw_address = s.address + cfg.gw_id = s.id + + local st = util.ubus("network.interface." .. interface, "status") + cfg.gw_ifname = st.device + + if not cfg.gw_address then + cfg.gw_address = st["ipv4-address"][1].address + end + + if not cfg.gw_id then + local devst = util.ubus("network.device", "status", {name = st.device}) + local macaddr = devst.macaddr + cfg.gw_id = macaddr:gsub(":", ""):upper() + end + end) + + c:foreach('wifidog-ng', 'server', function(s) + local host = s.host + local path = s.path or "/wifidog/" + local gw_port = cfg.gw_port + local gw_id = cfg.gw_id + local gw_address = cfg.gw_address + local ssid = cfg.ssid or "" + local proto, port = "http", "" + + + if s.port ~= "80" and s.port ~= "443" then + port = ":" .. s.port + end + + if s.ssl == "1" then + proto = "https" + end + + cfg.login_url = string.format("%s://%s%s%s%s?gw_address=%s&gw_port=%d&gw_id=%s&ssid=%s", + proto, host, port, path, s.login_path, gw_address, gw_port, gw_id, ssid) + + cfg.auth_url = string.format("%s://%s%s%s%s?gw_id=%s", + proto, host, port, path, s.auth_path, gw_id) + + cfg.ping_url = string.format("%s://%s%s%s%s?gw_id=%s", + proto, host, port, path, s.ping_path, gw_id) + + cfg.portal_url = string.format("%s://%s%s%s%s?gw_id=%s", + proto, host, port, path, s.portal_path, gw_id) + + cfg.msg_url = string.format("%s://%s%s%s%s?gw_id=%s", + proto, host, port, path, s.msg_path, gw_id) + end) + + cfg.parsed = true +end + +function M.get() + if not cfg.parsed then + M.parse() + end + + return cfg +end + +function M.add_whitelist(typ, value) + local c = uci.cursor() + local opt + + if typ == "mac" then + typ = "validated_user" + opt = "mac" + elseif typ == "domain" then + typ = "validated_domain" + opt = "domain" + else + return + end + + local exist = false + c:foreach("wifidog-ng", typ, function(s) + if s[opt] == value then + exist = true + end + end) + + if not exist then + local s = c:add("wifidog-ng", typ) + c:set("wifidog-ng", s, opt, value) + c:commit("wifidog-ng") + end +end + +function M.del_whitelist(typ, value) + local c = uci.cursor() + local opt + + if typ == "mac" then + typ = "validated_user" + opt = "mac" + elseif typ == "domain" then + typ = "validated_domain" + opt = "domain" + else + return + end + + c:foreach("wifidog-ng", typ, function(s) + if s[opt] == value then + c:delete("wifidog-ng", s[".name"]) + end + end) + + c:commit("wifidog-ng") +end + +return M diff --git a/net/wifidog-ng/files/wifidog-ng/heartbeat.lua b/net/wifidog-ng/files/wifidog-ng/heartbeat.lua new file mode 100644 index 000000000..0649be232 --- /dev/null +++ b/net/wifidog-ng/files/wifidog-ng/heartbeat.lua @@ -0,0 +1,46 @@ +--[[ + Copyright (C) 2018 Jianhui Zhao + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This program 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA + --]] + +local uloop = require "uloop" +local http = require "socket.http" +local util = require "wifidog-ng.util" +local config = require "wifidog-ng.config" + +local M = {} + +local timer = nil +local start_time = os.time() + +local function heartbeat() + local cfg = config.get() + + timer:set(1000 * cfg.checkinterval) + + local sysinfo = util.ubus("system", "info") + + local url = string.format("%s&sys_uptime=%d&sys_memfree=%d&sys_load=%d&wifidog_uptime=%d", + cfg.ping_url, sysinfo.uptime, sysinfo.memory.free, sysinfo.load[1], os.time() - start_time) + http.request(url) +end + +function M.start() + timer = uloop.timer(heartbeat, 1000) +end + +return M diff --git a/net/wifidog-ng/files/wifidog-ng/ubus.lua b/net/wifidog-ng/files/wifidog-ng/ubus.lua new file mode 100644 index 000000000..a2c57a2a2 --- /dev/null +++ b/net/wifidog-ng/files/wifidog-ng/ubus.lua @@ -0,0 +1,128 @@ +--[[ + Copyright (C) 2018 Jianhui Zhao + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This program 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA + --]] + +local uci = require "uci" +local ubus = require "ubus" +local http = require "socket.http" +local auth = require "wifidog-ng.auth" +local config = require "wifidog-ng.config" + + +local M = {} + +local conn = nil + +local ubus_codes = { + ["INVALID_COMMAND"] = 1, + ["INVALID_ARGUMENT"] = 2, + ["METHOD_NOT_FOUND"] = 3, + ["NOT_FOUND"] = 4, + ["NO_DATA"] = 5, + ["PERMISSION_DENIED"] = 6, + ["TIMEOUT"] = 7, + ["NOT_SUPPORTED"] = 8, + ["UNKNOWN_ERROR"] = 9, + ["CONNECTION_FAILED"] = 10 +} + +local function reload_validated_domain() + local c = uci.cursor() + + local file = io.open("/tmp/dnsmasq.d/wifidog-ng", "w") + + c:foreach("wifidog-ng", "validated_domain", function(s) + file:write("ipset=/" .. s.domain .. "/wifidog-ng-ip\n") + end) + file:close() + + os.execute("/etc/init.d/dnsmasq restart &") +end + +local methods = { + ["wifidog-ng"] = { + roam = { + function(req, msg) + local cfg = config.get() + + if not msg.ip or not msg.mac then + return ubus_codes["INVALID_ARGUMENT"] + end + + local url = string.format("%s&stage=roam&ip=%s&mac=%s", cfg.auth_url, msg.ip, msg.mac) + local r = http.request(url) or "" + local token = r:match("token=(%w+)") + if token then + auth.new_term(msg.ip, msg.mac, token) + end + end, {ip = ubus.STRING, mac = ubus.STRING } + }, + term = { + function(req, msg) + if msg.action == "show" then + conn:reply(req, {terms = auth.get_terms()}); + return + end + + if not msg.action or not msg.mac then + return ubus_codes["INVALID_ARGUMENT"] + end + + if msg.action == "add" then + auth.allow_user(mac) + elseif msg.action == "del" then + auth.deny_user(mac) + end + end, {action = ubus.STRING, mac = ubus.STRING } + }, + whitelist = { + function(req, msg) + if not msg.action or not msg.type or not msg.value then + return ubus_codes["INVALID_ARGUMENT"] + end + + if msg.action == "add" then + config.add_whitelist(msg.type, msg.value) + if msg.type == "mac" then + auth.allow_user(msg.value) + end + elseif msg.action == "del" then + config.del_whitelist(msg.type, msg.value) + if msg.type == "mac" then + auth.deny_user(msg.value) + end + end + + if msg.type == "domain" then + reload_validated_domain() + end + end, {action = ubus.STRING, type = ubus.STRING, value = ubus.STRING } + }, + } +} + +function M.init() + conn = ubus.connect() + if not conn then + error("Failed to connect to ubus") + end + + conn:add(methods) +end + +return M diff --git a/net/wifidog-ng/files/wifidog-ng/util.lua b/net/wifidog-ng/files/wifidog-ng/util.lua new file mode 100644 index 000000000..bf14e7d66 --- /dev/null +++ b/net/wifidog-ng/files/wifidog-ng/util.lua @@ -0,0 +1,83 @@ +--[[ + Copyright (C) 2018 Jianhui Zhao + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This program 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA + --]] + +local _ubus = require "ubus" +local _ubus_connection = nil + +local M = {} + +function M.arp_get(ifname, ipaddr) + for l in io.lines("/proc/net/arp") do + local f = {} + + for e in string.gmatch(l, "%S+") do + f[#f + 1] = e + end + + if f[1] == ipaddr and f[6] == ifname then + return f[4] + end + end +end + +function M.read_file(path, len) + local file = io.open(path, "r") + if not file then return nil end + + if not len then len = "*a" end + + local data = file:read(len) + file:close() + + return data +end + +local ubus_codes = { + "INVALID_COMMAND", + "INVALID_ARGUMENT", + "METHOD_NOT_FOUND", + "NOT_FOUND", + "NO_DATA", + "PERMISSION_DENIED", + "TIMEOUT", + "NOT_SUPPORTED", + "UNKNOWN_ERROR", + "CONNECTION_FAILED" +} + +function M.ubus(object, method, data) + if not _ubus_connection then + _ubus_connection = _ubus.connect() + assert(_ubus_connection, "Unable to establish ubus connection") + end + + if object and method then + if type(data) ~= "table" then + data = { } + end + local rv, err = _ubus_connection:call(object, method, data) + return rv, err, ubus_codes[err] + elseif object then + return _ubus_connection:signatures(object) + else + return _ubus_connection:objects() + end +end + +return M diff --git a/net/wifidog-ng/files/wifidog-ng/version.lua b/net/wifidog-ng/files/wifidog-ng/version.lua new file mode 100644 index 000000000..feb481979 --- /dev/null +++ b/net/wifidog-ng/files/wifidog-ng/version.lua @@ -0,0 +1,30 @@ +--[[ + Copyright (C) 2018 Jianhui Zhao + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This program 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + USA + --]] + +local M = { + MAJOR = 2, + MINOR = 0, + PATCH = 0 +} + +function M.string() + return string.format("%d.%d.%d", M.MAJOR, M.MINOR, M.PATCH) +end + +return M diff --git a/net/wifidog-ng/src/Makefile b/net/wifidog-ng/src/Makefile new file mode 100644 index 000000000..f829d5dfc --- /dev/null +++ b/net/wifidog-ng/src/Makefile @@ -0,0 +1,18 @@ +ifeq ($(findstring openwrt, $(CC)),) +ifneq ($(KERNELRELEASE),) + wifidog-ng-objs := main.o config.o + obj-m := wifidog-ng.o +else + KDIR = /lib/modules/$(shell uname -r)/build + +all: + make -C $(KDIR) M=$(PWD) modules + +clean: + rm -rf *.o *.ko *.mod.c Module.* modules.* .*.cmd .tmp* + +endif +else + wifidog-ng-objs := main.o config.o + obj-m := wifidog-ng.o +endif diff --git a/net/wifidog-ng/src/config.c b/net/wifidog-ng/src/config.c new file mode 100644 index 000000000..3ccad8438 --- /dev/null +++ b/net/wifidog-ng/src/config.c @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2017 jianhui zhao + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include + +#include "config.h" + +static struct proc_dir_entry *proc; +static struct config conf; + +static int update_gw_interface(const char *interface) +{ + int ret = 0; + struct net_device *dev; + struct in_device *in_dev; + + dev = dev_get_by_name(&init_net, interface); + if (!dev) { + pr_err("Not found interface: %s\n", interface); + return -ENOENT; + } + + conf.interface_ifindex = dev->ifindex; + + in_dev = inetdev_by_index(dev_net(dev), conf.interface_ifindex); + if (!in_dev) { + pr_err("Not found in_dev on %s\n", interface); + ret = -ENOENT; + goto QUIT; + } + + for_primary_ifa(in_dev) { + conf.interface_ipaddr = ifa->ifa_local; + conf.interface_mask = ifa->ifa_mask; + conf.interface_broadcast = ifa->ifa_broadcast; + + pr_info("Found ip from %s: %pI4\n", interface, &conf.interface_ipaddr); + break; + } endfor_ifa(in_dev) + +QUIT: + dev_put(dev); + + return ret; +} + +static int proc_config_show(struct seq_file *s, void *v) +{ + seq_printf(s, "enabled(RW) = %d\n", conf.enabled); + seq_printf(s, "interface(RW) = %s\n", conf.interface); + seq_printf(s, "ipaddr(RO) = %pI4\n", &conf.interface_ipaddr); + seq_printf(s, "netmask(RO) = %pI4\n", &conf.interface_mask); + seq_printf(s, "broadcast(RO) = %pI4\n", &conf.interface_broadcast); + seq_printf(s, "port(RW) = %d\n", conf.port); + seq_printf(s, "ssl_port(RW) = %d\n", conf.ssl_port); + + return 0; +} + +static ssize_t proc_config_write(struct file *file, const char __user *buf, size_t size, loff_t *ppos) +{ + char data[128]; + char *delim, *key; + const char *value; + int update = 0; + + if (size == 0) + return -EINVAL; + + if (size > sizeof(data)) + size = sizeof(data); + + if (copy_from_user(data, buf, size)) + return -EFAULT; + + data[size - 1] = 0; + + key = data; + while (key && *key) { + while (*key && (*key == ' ')) + key++; + + delim = strchr(key, '='); + if (!delim) + break; + + *delim++ = 0; + value = delim; + + delim = strchr(value, '\n'); + if (delim) + *delim++ = 0; + + if (!strcmp(key, "enabled")) { + conf.enabled = simple_strtol(value, NULL, 0); + if (conf.enabled) + update = 1; + pr_info("wifidog %s\n", conf.enabled ? "enabled" : "disabled"); + } else if (!strcmp(key, "interface")) { + strncpy(conf.interface, value, sizeof(conf.interface) - 1); + update = 1; + } else if (!strcmp(key, "port")) { + conf.port = simple_strtol(value, NULL, 0); + } else if (!strcmp(key, "ssl_port")) { + conf.ssl_port = simple_strtol(value, NULL, 0); + } + + key = delim; + } + + if (update) + update_gw_interface(conf.interface); + return size; +} + +static int proc_config_open(struct inode *inode, struct file *file) +{ + return single_open(file, proc_config_show, NULL); +} + +const static struct file_operations proc_config_ops = { + .owner = THIS_MODULE, + .open = proc_config_open, + .read = seq_read, + .write = proc_config_write, + .llseek = seq_lseek, + .release = single_release +}; + +int init_config(void) +{ + int ret = 0; + + conf.interface_ifindex= -1; + conf.port = 2060; + conf.ssl_port = 8443; + strcpy(conf.interface, "br-lan"); + + proc = proc_mkdir(PROC_DIR_NAME, NULL); + if (!proc) { + pr_err("can't create dir /proc/"PROC_DIR_NAME"/\n"); + return -ENODEV;; + } + + if (!proc_create("config", 0644, proc, &proc_config_ops)) { + pr_err("can't create file /proc/"PROC_DIR_NAME"/config\n"); + ret = -EINVAL; + goto remove; + } + + return 0; + +remove: + remove_proc_entry(PROC_DIR_NAME, NULL); + return ret; +} + +void deinit_config(void) +{ + remove_proc_entry("config", proc); + remove_proc_entry(PROC_DIR_NAME, NULL); +} + +struct config *get_config(void) +{ + return &conf; +} diff --git a/net/wifidog-ng/src/config.h b/net/wifidog-ng/src/config.h new file mode 100644 index 000000000..a7414affa --- /dev/null +++ b/net/wifidog-ng/src/config.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2017 jianhui zhao + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __CONFIG_H_ +#define __CONFIG_H_ + +#include + +#define PROC_DIR_NAME "wifidog-ng" + +struct config { + int enabled; + char interface[32]; + int interface_ifindex; + __be32 interface_ipaddr; + __be32 interface_mask; + __be32 interface_broadcast; + int port; + int ssl_port; +}; + +int init_config(void); +void deinit_config(void); +struct config *get_config(void); + +#endif diff --git a/net/wifidog-ng/src/main.c b/net/wifidog-ng/src/main.c new file mode 100644 index 000000000..96da9a667 --- /dev/null +++ b/net/wifidog-ng/src/main.c @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2017 jianhui zhao + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "utils.h" +#include "config.h" + +#define IPS_HIJACKED (1 << 31) +#define IPS_ALLOWED (1 << 30) + +static u32 wd_nf_nat_setup_info(void *priv, struct sk_buff *skb, + const struct nf_hook_state *state, struct nf_conn *ct) +{ + struct config *conf = get_config(); + struct tcphdr *tcph = tcp_hdr(skb); + union nf_conntrack_man_proto proto; + struct nf_nat_range newrange; + static uint16_t PORT_80 = htons(80); + + proto.tcp.port = (tcph->dest == PORT_80) ? htons(conf->port) : htons(conf->ssl_port); + newrange.flags = NF_NAT_RANGE_MAP_IPS | NF_NAT_RANGE_PROTO_SPECIFIED; + newrange.min_addr.ip = newrange.max_addr.ip = conf->interface_ipaddr; + newrange.min_proto = newrange.max_proto = proto; + + ct->status |= IPS_HIJACKED; + + return nf_nat_setup_info(ct, &newrange, NF_NAT_MANIP_DST); +} + +static u32 wifidog_hook(void *priv, struct sk_buff *skb, const struct nf_hook_state *state) +{ + struct config *conf = get_config(); + struct iphdr *iph = ip_hdr(skb); + struct nf_conn *ct; + struct tcphdr *tcph; + struct udphdr *udph; + enum ip_conntrack_info ctinfo; + static uint16_t PORT_80 = htons(80); /* http */ + static uint16_t PORT_443 = htons(443); /* https */ + static uint16_t PORT_67 = htons(67); /* dhcp */ + static uint16_t PORT_53 = htons(53); /* dns */ + + if (unlikely(!conf->enabled)) + return NF_ACCEPT; + + if (state->in->ifindex != conf->interface_ifindex) + return NF_ACCEPT; + + /* Accept broadcast */ + if (skb->pkt_type == PACKET_BROADCAST || skb->pkt_type == PACKET_MULTICAST) + return NF_ACCEPT; + + /* Accept all to local area networks */ + if ((iph->daddr | ~conf->interface_mask) == conf->interface_broadcast) + return NF_ACCEPT; + + ct = nf_ct_get(skb, &ctinfo); + if (!ct || (ct->status & IPS_ALLOWED)) + return NF_ACCEPT; + + if (ct->status & IPS_HIJACKED) { + if (is_allowed_mac(skb, state)) { + /* Avoid duplication of authentication */ + nf_reset(skb); + nf_ct_kill(ct); + } + return NF_ACCEPT; + } else if (ctinfo == IP_CT_NEW && (is_allowed_dest_ip(skb, state) || is_allowed_mac(skb, state))) { + ct->status |= IPS_ALLOWED; + return NF_ACCEPT; + } + + switch (iph->protocol) { + case IPPROTO_TCP: + tcph = tcp_hdr(skb); + if(tcph->dest == PORT_53 || tcph->dest == PORT_67) { + ct->status |= IPS_ALLOWED; + return NF_ACCEPT; + } + + if (tcph->dest == PORT_80 || tcph->dest == PORT_443) + goto redirect; + else + return NF_DROP; + + case IPPROTO_UDP: + udph = udp_hdr(skb); + if(udph->dest == PORT_53 || udph->dest == PORT_67) { + ct->status |= IPS_ALLOWED; + return NF_ACCEPT; + } + return NF_DROP; + + default: + ct->status |= IPS_ALLOWED; + return NF_ACCEPT; + } + +redirect: + /* all packets from unknown client are dropped */ + if (ctinfo != IP_CT_NEW || (ct->status & IPS_DST_NAT_DONE)) { + pr_debug("dropping packets of suspect stream, src:%pI4, dst:%pI4\n", &iph->saddr, &iph->daddr); + return NF_DROP; + } + + return nf_nat_ipv4_in(priv, skb, state, wd_nf_nat_setup_info); +} + +static struct nf_hook_ops wifidog_ops[] __read_mostly = { + { + .hook = wifidog_hook, + .pf = PF_INET, + .hooknum = NF_INET_PRE_ROUTING, + .priority = NF_IP_PRI_CONNTRACK + 1 /* after conntrack */ + } +}; + +static int __init wifidog_init(void) +{ + int ret; + + ret = init_config(); + if (ret) + return ret; + +#if LINUX_VERSION_CODE > KERNEL_VERSION(4, 12, 14) + ret = nf_register_net_hooks(&init_net, wifidog_ops, ARRAY_SIZE(wifidog_ops)); +#else + ret = nf_register_hooks(wifidog_ops, ARRAY_SIZE(wifidog_ops)); +#endif + if (ret < 0) { + pr_err("can't register hook\n"); + goto remove_config; + } + + pr_info("kmod of wifidog is started\n"); + + return 0; + +remove_config: + deinit_config(); + return ret; +} + +static void __exit wifidog_exit(void) +{ + deinit_config(); + +#if LINUX_VERSION_CODE > KERNEL_VERSION(4, 12, 14) + nf_unregister_net_hooks(&init_net, wifidog_ops, ARRAY_SIZE(wifidog_ops)); +#else + nf_unregister_hooks(wifidog_ops, ARRAY_SIZE(wifidog_ops)); +#endif + + pr_info("kmod of wifidog-ng is stop\n"); +} + +module_init(wifidog_init); +module_exit(wifidog_exit); + +MODULE_AUTHOR("jianhui zhao "); +MODULE_LICENSE("GPL"); diff --git a/net/wifidog-ng/src/utils.h b/net/wifidog-ng/src/utils.h new file mode 100644 index 000000000..5f7aa08c4 --- /dev/null +++ b/net/wifidog-ng/src/utils.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2017 jianhui zhao + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __UTILS_H_ +#define __UTILS_H_ + +#include + +static inline int wd_ip_set_test(const char *name, const struct sk_buff *skb, + struct ip_set_adt_opt *opt, const struct nf_hook_state *state) +{ + static struct xt_action_param par = { }; + struct ip_set *set = NULL; + ip_set_id_t index; + int ret; + + index = ip_set_get_byname(state->net, name, &set); + if (!set) + return 0; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 10, 0) + par.net = state->net; +#else + par.state = state; +#endif + + ret = ip_set_test(index, skb, &par, opt); + ip_set_put_byindex(state->net, index); + return ret; +} + +static inline int is_allowed_mac(struct sk_buff *skb, const struct nf_hook_state *state) +{ + static struct ip_set_adt_opt opt = { + .family = NFPROTO_IPV4, + .dim = IPSET_DIM_ONE, + .flags = IPSET_DIM_ONE_SRC, + .ext.timeout = UINT_MAX, + }; + + return wd_ip_set_test("wifidog-ng-mac", skb, &opt, state); +} + +static inline int is_allowed_dest_ip(struct sk_buff *skb, const struct nf_hook_state *state) +{ + static struct ip_set_adt_opt opt = { + .family = NFPROTO_IPV4, + .dim = IPSET_DIM_ONE, + .ext.timeout = UINT_MAX, + }; + + return wd_ip_set_test("wifidog-ng-ip", skb, &opt, state); +} + +#endif