├── .gitignore ├── Makefile ├── Makefile_build_standalone ├── README.md ├── dist └── etc │ └── config │ └── kikiauth └── luasrc ├── controller └── kikiauth │ ├── admin.lua │ └── authserver.lua ├── model └── cbi │ └── kikiauth-admin │ ├── services.lua │ └── status.lua └── view └── kikiauth ├── auth.htm ├── facebookcallback.htm ├── gatewaymessage.htm ├── googlecallback.htm └── login.htm /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | Makefile 3 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | include ../../build/config.mk 2 | include ../../build/module.mk -------------------------------------------------------------------------------- /Makefile_build_standalone: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2009 3 | # 4 | # This is free software, licensed under the Apache License, Version 2.0 . 5 | # 6 | 7 | include $(TOPDIR)/rules.mk 8 | 9 | PKG_NAME:=luci-app-kikiauth 10 | PKG_RELEASE:=1 11 | 12 | include $(INCLUDE_DIR)/package.mk 13 | 14 | define Package/$(PKG_NAME) 15 | SECTION:=luci 16 | CATEGORY:=LuCI 17 | SUBMENU:=3rd Party 18 | TITLE:=Splash login with OAuth 19 | DEPENDS:=luci-lib-json wget wifidog 20 | endef 21 | 22 | define Package/$(PKG_NAME)/description 23 | Auth server for WifiDog, provide external OAuth login method. 24 | endef 25 | 26 | define Build/Prepare 27 | for d in luasrc root; do \ 28 | if [ -d ./$$$$d ]; then \ 29 | mkdir -p $(PKG_BUILD_DIR)/$$$$d; \ 30 | $(CP) ./$$$$d/* $(PKG_BUILD_DIR)/$$$$d/; \ 31 | fi; \ 32 | done 33 | endef 34 | 35 | define Build/Configure 36 | endef 37 | 38 | define Build/Compile 39 | endef 40 | 41 | HTDOCS = /www 42 | LUA_LIBRARYDIR = /usr/lib/lua 43 | LUCI_LIBRARYDIR = $(LUA_LIBRARYDIR)/luci 44 | 45 | define Package/$(PKG_NAME)/install 46 | if [ -d $(PKG_BUILD_DIR)/luasrc ]; then \ 47 | $(INSTALL_DIR) $(1)$(LUCI_LIBRARYDIR); \ 48 | cp -pR $(PKG_BUILD_DIR)/luasrc/* $(1)$(LUCI_LIBRARYDIR)/; \ 49 | else true; fi 50 | if [ -d $(PKG_BUILD_DIR)/htdocs ]; then \ 51 | $(INSTALL_DIR) $(1)$(HTDOCS); \ 52 | cp -pR $(PKG_BUILD_DIR)/htdocs/* $(1)$(HTDOCS)/; \ 53 | else true; fi 54 | if [ -d $(PKG_BUILD_DIR)/root ]; then \ 55 | $(INSTALL_DIR) $(1)/; \ 56 | cp -pR $(PKG_BUILD_DIR)/root/* $(1)/; \ 57 | else true; fi 58 | endef 59 | 60 | $(eval $(call BuildPackage,$(PKG_NAME))) 61 | 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | KikiAuth 2 | ======== 3 | 4 | KikiAuth is based on LuCI, providing an alternative to Auth Server for WifiDog. 5 | KikiAuth aims to support authentication via OAuth services (Google, Facebook, Twitter...) only and run on the same box as WifiDog (no need to setup a separated machine for authentication). 6 | 7 | Important note 8 | -------------- 9 | 10 | The project is halted, because of these obstacles: 11 | 12 | - Entire Facebook website is on HTTPS. It means that if we let user to login to Facebook, we have to open all traffic to Facebook website. It means that even before logging in our splash screen, user still can use Facebook, Google. These sites are open to allow OAuth login. 13 | 14 | - The firewall open the traffic based on destination IP address, not domain. It means that we have to find all IP addresses of facebook.com and other Facebook owned domains. But due to Facebook's load balancing mechanism, each time we query, the DNS returns a different set of IP addresses. The set of IP address also become invalid after a while, and come back valid after another time. 15 | 16 | - Facebook doesn't use only facebook.com. It also uses various domains for other resource (JS, CSS). These are also not fixed and can be changed any time. 17 | 18 | I can only have workaround for the second issue, by making the router to periodically retrieve new IP addresses for a set of known domain. But still, the overall is not reliable. 19 | 20 | Build 21 | ----- 22 | 23 | You must have a copy of LuCI source tree (luci-0.10). 24 | Copy KikiAuth folder to luci-0.10/applications. 25 | 26 | Run 27 | 28 | make runhttpd 29 | 30 | to compile. 31 | 32 | Build ipk package 33 | ----- 34 | 35 | - Copy the folder to openwrt/package (source tree) 36 | - Rename Makefile_build_standalone to Makefile (replace the old Makefile) 37 | - Rename "dist" folder to "root" 38 | - Choose the luci-app-kikiauth in `make menuconfig`. 39 | - Run `make package/luci-app-kikiauth/compile V=99` to build. 40 | -------------------------------------------------------------------------------- /dist/etc/config/kikiauth: -------------------------------------------------------------------------------- 1 | 2 | config 'oauth_services' 'facebook' 3 | option 'enabled' '1' 4 | option 'app_id' '420756987974770' 5 | option 'redirect_uri' 'http://openwrt.lan/cgi-bin/luci/kikiauth/oauth/facebookcallback' 6 | list 'googleapps' 'mbm.vn' 7 | list 'googleapps' 'pfiev.net' 8 | list 'ips' 'www-slb-10-01-prn1.facebook.com', 9 | list 'ips' 'www-slb-11-12-prn1.facebook.com', 10 | list 'ips' 's-static.ak.fbcdn.net' 11 | 12 | config 'oauth_services' 'google' 13 | option 'enabled' '1' 14 | option 'app_id' '396818136722.apps.googleusercontent.com' 15 | option 'redirect_uri' 'https://kikiauth.appspot.com/google' 16 | list 'ips' 'accounts.l.google.com' 17 | list 'ips' 'accounts-cctld.l.google.com' 18 | list 'ips' 'clients.l.google.com' 19 | list 'ips' 'googlehosted.l.googleusercontent.com' 20 | list 'ips' 'ssl.gstatic.com' 21 | list 'ips' 'kikiauth.appspot.com' 22 | 23 | config 'oauth_services' 'twitter' 24 | 25 | config 'success_page' 'oauth_success_page' 26 | option 'success_text' '

WELCOME!!!


You have been granted Internet access permission!

' 27 | 28 | -------------------------------------------------------------------------------- /luasrc/controller/kikiauth/admin.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Admin page for KikiAuth - the replacement for WifiDog's auth server, 3 | to provide OAuth support. 4 | 5 | Copyright 2012 Nguyen Hong Quan 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | $Id$ 14 | ]]-- 15 | 16 | module("luci.controller.kikiauth.admin", package.seeall) 17 | 18 | function index() 19 | if not nixio.fs.access("/etc/config/kikiauth") then 20 | return 21 | end 22 | 23 | entry({"admin", "network", "kikiauth"}, alias("admin", "network", "kikiauth", "services"), _("KikiAuth")).i18n = "kikiauth" 24 | entry({"admin", "network", "kikiauth", "status"}, cbi("kikiauth-admin/status"), _("Status")) 25 | entry({"admin", "network", "kikiauth", "services"}, cbi("kikiauth-admin/services"), _("Services")) 26 | end 27 | -------------------------------------------------------------------------------- /luasrc/controller/kikiauth/authserver.lua: -------------------------------------------------------------------------------- 1 | module("luci.controller.kikiauth.authserver", package.seeall) 2 | 3 | require "luci.json" 4 | local fs = require("nixio.fs") 5 | 6 | -- Name of iptable chain, in which we will open access 7 | -- to OAuth services (Facebook, Google). 8 | -- This chain will be in NAT table and FILTER table. 9 | local chain = "KikiAuth" 10 | local temp_ip_file = '/tmp/kikiauth_ips' 11 | local iplist_threshold = 5 12 | 13 | 14 | -- === String utilities ==== 15 | 16 | -- Check if a string starts with given prefix 17 | function string.startswith(self, prefix) 18 | local ret = false 19 | if prefix ~= nil then 20 | ret = (self:sub(1, string.len(prefix)) == prefix) 21 | end 22 | return ret 23 | end 24 | 25 | -- Check if a string ends with given suffix 26 | function string.endswith(self, suffix) 27 | local ret = false 28 | if suffix ~= nil then 29 | local offset = self:len() - suffix:len() 30 | ret = (self:sub(offset + 1) == suffix) 31 | end 32 | return ret 33 | end 34 | 35 | function index() 36 | entry({"kikiauth", "ping"}, call("action_say_pong"), "Click here", 10).dependent=false 37 | entry({"kikiauth", "auth"}, call("action_auth_response_to_gw"), "", 20).dependent=false 38 | entry({"kikiauth", "portal"}, call("action_redirect_to_success_page"), "Success page", 30).dependent=false 39 | entry({"kikiauth", "login"}, template("kikiauth/login"), "Login page", 40).dependent=false 40 | entry({"kikiauth", "oauth", "googlecallback"}, template("kikiauth/googlecallback"), "", 50).dependent=false 41 | entry({"kikiauth", "oauth", "facebookcallback"}, template("kikiauth/facebookcallback"), "", 60).dependent=false 42 | entry({"kikiauth", "gw_message.php"}, template("kikiauth/gatewaymessage"), "", 70).dependent=false 43 | end 44 | 45 | function action_say_pong() 46 | luci.http.prepare_content("text/plain") 47 | luci.http.write("Pong") 48 | local enabled_OAuth_service_list = get_enabled_OAuth_service_list() 49 | check_ip_list_of_enabled_OAuth_services(enabled_OAuth_service_list) 50 | --find and add new ip 51 | for _, service in ipairs(enabled_OAuth_service_list) do 52 | find_and_add_new_IP(service) 53 | end 54 | end 55 | 56 | function get_enabled_OAuth_service_list() 57 | local uci = require "luci.model.uci".cursor() 58 | local enabled_OAuth_service_list = {} 59 | local function check_service_enabled(sect) 60 | if sect.enabled == '1' then 61 | local name = sect['.name'] 62 | table.insert(enabled_OAuth_service_list, name) 63 | end 64 | end 65 | uci:foreach("kikiauth", "oauth_services", check_service_enabled) 66 | return enabled_OAuth_service_list 67 | end 68 | 69 | function find_and_add_new_IP(service) 70 | local dynamic_domains = {} -- List of domains which has IP changing by time. 71 | if service == "facebook" then 72 | dynamic_domains = {'www.facebook.com', 's-static.ak.fbcdn.net'} 73 | elseif service == "google" then 74 | dynamic_domains = {'www.google.com'} 75 | end 76 | 77 | -- No domain, do nothing 78 | if dynamic_domains == {} then return end 79 | 80 | local ips = get_oauth_ip_list(service) 81 | local updated = false 82 | for _, d in ipairs(dynamic_domains) do 83 | local output = luci.sys.exec("ping -c 1 %s | grep 'bytes from' | awk '{print $4}'" % {d}) 84 | -- The output is like "77.77.77.77:" 85 | -- Note that this is the output of ping command on OpenWrt. On other distro (Ubuntu), 86 | -- it may be different. 87 | if output then 88 | local ping_ip = luci.util.trim(output):sub(1, -2) 89 | if not luci.util.contains(ips, ping_ip) then 90 | table.insert(ips, ping_ip) 91 | iptables_kikiauth_add_iprule(ping_ip) 92 | updated = true 93 | end 94 | end 95 | end 96 | 97 | -- If there is no new IP, stop the function here 98 | if not updated then 99 | return 100 | end 101 | -- Otherwise, store the IPs 102 | store_ips(service, ips) 103 | end 104 | 105 | function store_ips(service, ips) 106 | -- Store the IP list to RAM (/tmp folder) first. 107 | -- We should not write to /etc/ too frequently 108 | -- because this memory is easy to be worn out. 109 | fs.writefile(temp_ip_file..service, table.concat(ips, ' ')) 110 | 111 | -- We will sometimes update the IP list in /etc/config/kikiauth file. 112 | -- Use use random number to decide if we will update the file. 113 | math.randomseed(os.time()) -- We have to drop seed, or the first generated number will always be 85! 114 | local n = math.random(100) 115 | if n > 40 and n < 60 then 116 | local uci = luci.model.uci.cursor() 117 | uci:set_list("kikiauth", service, "ips", ips) 118 | uci:save("kikiauth") 119 | uci:commit("kikiauth") 120 | end 121 | end 122 | 123 | -- the following code is for checking the enabled OAuth service IPs list. 124 | -- It first get out the day and time in the settings, and then, 125 | -- if it's time to check it will check. 126 | function check_ip_list_of_enabled_OAuth_services(enabled_OAuth_service_list) 127 | for _, service in ipairs(enabled_OAuth_service_list) do 128 | local uci = require "luci.model.uci".cursor() 129 | local check_enabled = uci:get("kikiauth", service, "check_enabled") 130 | if check_enabled ~= nil then 131 | local day = uci:get("kikiauth", service, "day") 132 | local time = uci:get("kikiauth", service, "time") 133 | -- search_pattern is for 'time' checking. In this situation, 134 | -- we want to check if the current time and 135 | -- the one in the setting is different to each other within the range of 3 minutes. 136 | local search_pattern = time .. ":0[012]" 137 | --check if the current day and time match the ones in the settings. 138 | if string.find(os.date(), day) ~= nil or day == "Every" 139 | and string.find(os.date(), search_pattern) ~= nil then 140 | check_valid_ips(service) 141 | end 142 | end 143 | end 144 | end 145 | 146 | -- Check a particular service IPs list 147 | -- @param service: "facebook" or "google" ... 148 | -- Invalid IP will be removed. 149 | function check_valid_ips(service) 150 | local ips = get_oauth_ip_list(service) 151 | local newips = {} 152 | for _, ip in ipairs(ips) do 153 | local output = luci.sys.exec("ping -c 2 %s | grep '64 bytes' | awk '{print $1}'" % {ip}) 154 | if output and output:find("64") then table.insert(newips, ip) 155 | else iptables_kikiauth_delete_iprule(ip) end 156 | end 157 | if #ips ~= #newips then 158 | store_ips(service, ips) 159 | end 160 | end 161 | 162 | function action_redirect_to_success_page() 163 | local uci = require "luci.model.uci".cursor() 164 | local success_url = uci:get("kikiauth","oauth_success_page","success_url") 165 | -- If the admin provides an URL, use it to redirect the client to. If not, redirect the client to his original request. 166 | if success_url ~= nil then 167 | -- fix bug when the admin only enters a white-space string. 168 | -- In this case, we also redirect the client to his original request. 169 | if luci.util.trim(success_url) ~= "" then 170 | luci.http.redirect(success_url) 171 | else 172 | local success_text = uci:get("kikiauth", "oauth_success_page", "success_text") 173 | luci.http.write(success_text) 174 | --local sauth = require "luci.sauth" 175 | --local original_url = sauth.read("abc") 176 | --luci.http.redirect(original_url) 177 | --return 178 | end 179 | else 180 | local success_text = uci:get("kikiauth", "oauth_success_page", "success_text") 181 | luci.http.write(success_text) 182 | --local sauth = require "luci.sauth" 183 | --local original_url = sauth.read("abc") 184 | --luci.http.redirect(original_url) 185 | end 186 | end 187 | 188 | function action_auth_response_to_gw() 189 | local token = luci.http.formvalue("token") 190 | local url = nil 191 | local response = '' 192 | local actual_token = '' 193 | local domain = nil 194 | local resp = nil 195 | 196 | -- token will be like 'facebook_xxxxxxxxx' or 'google_xxxxxxx' 197 | -- or 'google_mbm.vn__xxxxxxx' (with Google Apps domain) 198 | if token:startswith('facebook_') then 199 | actual_token = token:sub(10) 200 | url = "https://graph.facebook.com/me?access_token=%s" % {actual_token} 201 | elseif token:startswith('google_') then 202 | local rest = token:sub(8) 203 | domain = rest:match('^([%-a-z0-9%.]+%.[a-z]+)__') 204 | if domain then 205 | actual_token = rest:sub(domain:len() + 3) 206 | else 207 | actual_token = rest 208 | end 209 | url = "https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=%s" % {actual_token} 210 | end 211 | 212 | if url then 213 | response = luci.util.exec("wget --no-check-certificate -qO- %s" % {url}) 214 | end 215 | 216 | if not response or response == "" then 217 | luci.http.write("Auth: 6") 218 | return 219 | end 220 | 221 | if token:startswith('facebook_') and string.find(response, "id", 1) ~= nil then 222 | luci.http.write("Auth: 1") 223 | return 224 | end 225 | 226 | if token:startswith('google_') and belongto_googleapps_domain(response, domain) then 227 | luci.http.write("Auth: 1") 228 | return 229 | end 230 | 231 | luci.http.write("Auth: 6") 232 | return 233 | end 234 | 235 | -- Check if the logged in email belongs to a Google Apps domain 236 | function belongto_googleapps_domain(response, domain) 237 | -- If domain is not given, accept 238 | if domain == nil then return true end 239 | 240 | -- Get the part behind 'www.' 241 | if domain:startswith('www.') then 242 | domain = domain:sub(5) 243 | end 244 | local resp = luci.json.decode(response) 245 | if not resp then return false end 246 | 247 | local email = resp.email 248 | local verified_email = resp.verified_email 249 | 250 | if not verified_email then return false end 251 | 252 | local d = email:sub(email:find('@') + 1) 253 | return (d == domain) 254 | end 255 | 256 | -- Get IP list from temp file 257 | function ip_list_from_temp(service) 258 | local filepath = temp_ip_file..service 259 | if not fs.access(filepath) then 260 | return {} 261 | end 262 | local content = fs.readfile(filepath) 263 | return luci.util.split(content, ' ') 264 | end 265 | 266 | function ip_list_from_config(service) 267 | local x = luci.model.uci.cursor() 268 | local lip = x:get_list('kikiauth', service, 'ips') 269 | return to_ip_list(lip) 270 | end 271 | 272 | -- Get IP list for an OAuth service. 273 | -- @param service "facebook" or "google" 274 | function get_oauth_ip_list(service) 275 | -- Get from /tmp first 276 | local ips_temp = ip_list_from_temp(service) 277 | -- If this list was populated enough 278 | if #ips_temp > iplist_threshold then 279 | return ips_temp 280 | end 281 | return ip_list_from_config(service) 282 | end 283 | 284 | function to_ip_list(mixlist) 285 | if mixlist == nil then 286 | return {} 287 | end 288 | 289 | local allip = {} 290 | -- Convert from hostname (www-slb-10-01-prn1.facebook.com) 291 | -- to IP. 292 | for n, ip in ipairs(mixlist) do 293 | -- Check if ip is a hostname 294 | if not ip:match('^%d+.%d+.%d+.%d+$') then 295 | allip = luci.util.combine(allip, hostname_to_ips(ip)) 296 | else 297 | table.insert(allip, ip) 298 | end 299 | end 300 | return allip 301 | end 302 | 303 | function hostname_to_ips(host) 304 | local l = {} 305 | local rs = nixio.getaddrinfo(host, 'inet', 'https') 306 | if not rs then 307 | return l; 308 | end 309 | for i, r in pairs(rs) do 310 | if r.socktype == 'stream' then table.insert(l, r.address) end 311 | end 312 | return l 313 | end 314 | 315 | function iptables_kikiauth_chain_exist() 316 | return (iptables_kikiauth_chain_exist_in_table('nat') 317 | and iptables_kikiauth_chain_exist_in_table('filter')) 318 | end 319 | 320 | function check_fb_ip2() 321 | local httpc = require "luci.httpclient" 322 | local uci = require "luci.model.uci".cursor() 323 | local ips = {} 324 | ips = uci:get_list("kikiauth", "facebook", "ips") 325 | for i = 1, #ips do 326 | -- the "if" is used to fix the bug of accessing a nil value of the "ips" table 327 | -- (because when one element is removed, 328 | -- the length of the ips table is correspondingly subtracted by 1). 329 | if ips[i] == nil then 330 | break 331 | end 332 | local res, code, msg = httpc.request_to_buffer("http://"..ips[i]) 333 | print(code, msg) 334 | if code == -2 then 335 | table.remove(ips, i) 336 | -- we have to subtract "i" by 1 to keep track of the correct index of the 'ips' table 337 | -- that we want to loop in the next route because after removing an element, 338 | -- the next element will fill the removed position. 339 | i = i - 1 340 | end 341 | end 342 | for i=1,# ips do 343 | print(i, ips[i]) 344 | end 345 | end 346 | 347 | function iptables_kikiauth_chain_exist_in_table(tname) 348 | local count = 0 349 | for line in luci.util.execi("iptables-save -t %s | grep %s" % {tname, chain}) do 350 | line = luci.util.trim(line) 351 | if count == 0 and line:startswith(":%s" % {chain}) then 352 | count = count + 1 353 | elseif count == 1 and line:endswith("-j %s" % {chain}) then 354 | count = count + 1 355 | break 356 | end 357 | end -- If check OK, count == 2 now 358 | return (count > 1) 359 | end 360 | 361 | function iptables_kikiauth_create_chain() 362 | return (iptables_kikiauth_create_chain_in_table('nat') 363 | and iptables_kikiauth_create_chain_in_table('filter')) 364 | end 365 | 366 | function iptables_kikiauth_create_chain_in_table(tname) 367 | local r = 0 368 | luci.sys.call("iptables -t %s -N %s" % {tname, chain}) 369 | local rootchain = 'PREROUTING' 370 | if tname == 'filter' then rootchain = 'FORWARD' end 371 | r = r + luci.sys.call("iptables -t %s -A %s -j %s" % {tname, rootchain, chain}) 372 | return (r == 0) -- Convert from zero (success) to true 373 | end 374 | 375 | -- Move the KikiAuth chain to suitable position between WifiDog chains 376 | function iptables_kikiauth_insert_to_wifidog() 377 | -- Get the name of WifiDog's WiFi2Internet chain. 378 | -- WiFiDog_eth0_WIFI2Internet or similar 379 | local c = "iptables -t filter -S FORWARD | egrep -io 'WiFiDog_[a-z0-9]+_WIFI2Internet'" 380 | local wd_internet_chname = luci.util.trim(luci.util.exec(c)) 381 | if wd_internet_chname == '' then 382 | return 383 | end 384 | -- Get the name of WiFiDog's AuthServers. 385 | -- WiFiDog_eth0_AuthServers or similar 386 | c = "iptables -t filter -S %s | egrep -io 'WiFiDog_[a-z0-9]+_AuthServers'" % {wd_internet_chname} 387 | local wd_authserver_chname = luci.util.trim(luci.util.exec(c)) 388 | if wd_authserver_chname == '' then 389 | return 390 | end 391 | -- Determine the position of AuthServer rule in WiFi2Internet chain 392 | c = "iptables -t filter -S %s | grep -i '\\-A '" % {wd_internet_chname} 393 | local pos = 0 394 | for line in luci.util.execi(c) do 395 | pos = pos + 1 396 | if line:find(wd_authserver_chname) then break end 397 | end 398 | -- Insert KikiAuth rule right after 399 | c = "iptables -t filter -I %s %d -j %s" % {wd_internet_chname, pos+1, chain} 400 | luci.sys.call(c) 401 | -- Remove KikiAuth rule from FORWARD chain 402 | c = "iptables -t filter -D FORWARD -j %s" % {chain} 403 | luci.sys.call(c) 404 | end 405 | 406 | function iptables_kikiauth_remove_from_wifidog() 407 | local r = 0 408 | -- Get the name of WifiDog's WiFi2Internet chain. 409 | -- WiFiDog_eth0_WIFI2Internet or similar 410 | local c = "iptables -t filter -S FORWARD | egrep -io 'WiFiDog_[a-z0-9]+_WIFI2Internet'" 411 | local wd_internet_chname = luci.util.trim(luci.util.exec(c)) 412 | if wd_internet_chname == '' then 413 | return 414 | end 415 | -- Delete KikiAuth rule from Wifidog 416 | while r == 0 do 417 | c = "iptables -t filter -D %s -j %s" % {wd_internet_chname, chain} 418 | r = luci.sys.call(c) 419 | end 420 | return (r == 0) 421 | end 422 | 423 | function iptables_kikiauth_delete_chain_from_table(tname) 424 | local r = 0 425 | luci.sys.call("iptables -t %s -F %s" % {tname, chain}) 426 | local rootchain = 'PREROUTING' 427 | if tname == 'filter' then rootchain = 'FORWARD' end 428 | while r == 0 do 429 | r = luci.sys.call("iptables -t %s -D %s -j %s" % {tname, rootchain, chain}) 430 | end 431 | luci.sys.call("iptables -t %s -X %s" % {tname, chain}) 432 | luci.sys.call(c) 433 | return (not iptables_kikiauth_chain_exist()) 434 | end 435 | 436 | function iptables_kikiauth_delete_chain() 437 | iptables_kikiauth_remove_from_wifidog() 438 | return (iptables_kikiauth_delete_chain_from_table('filter') 439 | and iptables_kikiauth_delete_chain_from_table('nat')) 440 | end 441 | 442 | function iptables_kikiauth_add_iprule(address, excluded) 443 | local l 444 | if address:match('^%d+.%d+.%d+.%d+$') then 445 | l = {address} 446 | else 447 | l = hostname_to_ips(address) 448 | end 449 | -- Chain nil variable to {} to avoid exception at luci.util.contains 450 | if excluded == nil then excluded = {} end 451 | for _, ip in ipairs(l) do 452 | if not luci.util.contains(excluded, ip) then 453 | local c = "iptables -t nat -A %s -d %s -p tcp --dport 443 -j ACCEPT" % {chain, ip} 454 | luci.sys.call(c) 455 | local c = "iptables -t filter -A %s -d %s -p tcp --dport 443 -j ACCEPT" % {chain, ip} 456 | luci.sys.call(c) 457 | end 458 | end 459 | end 460 | 461 | function iptables_kikiauth_get_ip_list() 462 | local l = {} 463 | for line in luci.util.execi("iptables-save -t nat | grep '%s -d'" % {chain}) do 464 | table.insert(l, line:match('%-d (%d+.%d+.%d+.%d+)')) 465 | end 466 | return l 467 | end 468 | 469 | function iptables_kikiauth_delete_iprule(iplist) 470 | for _, ip in ipairs(iplist) do 471 | local c = "iptables -t nat -D %s -d %s -p tcp -m tcp --dport 443 -j ACCEPT" % {chain, ip} 472 | luci.sys.call(c) 473 | c = "iptables -t filter-D %s -d %s -p tcp -m tcp --dport 443 -j ACCEPT" % {chain, ip} 474 | luci.sys.call(c) 475 | end 476 | -- If there is no rule left, delete the chain 477 | local existing = iptables_kikiauth_get_ip_list() 478 | if existing == {} then 479 | iptables_kikiauth_delete_chain() 480 | end 481 | end -------------------------------------------------------------------------------- /luasrc/model/cbi/kikiauth-admin/services.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Admin page for KikiAuth - the replacement for WifiDog's auth server, 3 | to provide OAuth support. 4 | 5 | Copyright 2012 Nguyen Hong Quan 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | $Id$ 14 | ]]-- 15 | authserver = require "luci.controller.kikiauth.authserver" 16 | local class = luci.util.class 17 | 18 | SerFlag = class(Flag) 19 | function SerFlag.write(self, section, value) 20 | if value == '1' then 21 | if not authserver.iptables_kikiauth_chain_exist() then 22 | authserver.iptables_kikiauth_create_chain() 23 | end 24 | -- We write the default value for "ips" option below. 25 | -- Due to a bug (?), this value is not written. 26 | if self.section.fields['ips'] then 27 | local default_ips = self.section.fields['ips'].default 28 | local ip_option = self.section.fields['ips'] 29 | ip_option:write(section, default_ips) 30 | end 31 | end 32 | Flag.write(self, section, value) 33 | end 34 | 35 | function SerFlag.remove(self, section) 36 | --[[ 37 | local dd = os.date('%H:%M:%S') 38 | local l = "%s remove %s" % {dd, section} 39 | luci.sys.call("echo '%s' >> /tmp/log_kikiauth.txt" % {l}) 40 | --]] 41 | 42 | -- Delete iptables rules for IPs belonging to this service. 43 | local old_own_ips = self.map:get(section, "ips") 44 | old_own_ips = authserver.to_ip_list(old_own_ips) 45 | authserver.iptables_kikiauth_delete_iprule(old_own_ips) 46 | Flag.remove(self, section) 47 | end 48 | 49 | 50 | IPList = class(DynamicList) 51 | function IPList.cfgvalue(self, section) 52 | return DynamicList.cfgvalue(self, section) or self.default 53 | end 54 | 55 | function IPList.write(self, section, value) 56 | -- There is a bug (?) from CBI, by which the default value is not sent to write. 57 | if authserver.iptables_kikiauth_chain_exist() then 58 | local added = authserver.iptables_kikiauth_get_ip_list() 59 | for _, address in ipairs(value) do 60 | authserver.iptables_kikiauth_add_iprule(address, added) 61 | end 62 | authserver.iptables_kikiauth_insert_to_wifidog() 63 | end 64 | DynamicList.write(self, section, value) 65 | end 66 | function IPList.remove(self, section) 67 | -- Get only old IPs of this section. Don't touch other's. 68 | local old_own_ips = self.map:get(section, self.option) 69 | old_own_ips = authserver.to_ip_list(old_own_ips) 70 | authserver.iptables_kikiauth_delete_iprule(old_own_ips) 71 | DynamicList.remove(self, section) 72 | end 73 | 74 | m = Map("kikiauth", "KikiAuth", translate("KikiAuth creates a captive portal to work with WifiDog. KikiAuth support logging in with popular OAuth services account.")) -- We want to edit the uci config file /etc/config/kikiauth 75 | 76 | s = m:section(NamedSection, "facebook", "oauth_services", "Facebook", 77 | translate("You can register your own Facebook app and use its parameters here.")) 78 | 79 | s:tab("general", translate("General settings")) 80 | s:tab("ip", translate("Service IP addresses")) 81 | 82 | e = s:taboption("general", SerFlag, "enabled", translate("Enabled?")) 83 | 84 | ---***--- 85 | p = s:taboption("general", Value, "app_id", "App ID/ Client ID", 86 | translate("The App ID/API Key of your \ 87 | registered Facebook app.")) 88 | p:depends('enabled', '1') 89 | p.default = '420756987974770' 90 | p = s:taboption("general", Value, "redirect_uri", "Redirect URI", 91 | translate("This URI has to be match the one you registered for your Facebook app.")) 92 | p:depends('enabled', '1') 93 | p.default = 'http://openwrt.lan/cgi-bin/luci/kikiauth/oauth/facebookcallback' 94 | 95 | ---***--- 96 | p = s:taboption("ip", IPList, "ips", "Facebook IPs",translate("List of Facebook IPs used for the gateway to open the traffic correctly while using Facebook OAuth.")) 97 | p:depends('enabled', '1') 98 | p.default = {'www-slb-10-01-prn1.facebook.com', 99 | 'www-slb-11-12-prn1.facebook.com', 100 | 's-static.ak.fbcdn.net'} 101 | 102 | ---***--- 103 | p = s:taboption("ip", Flag, "check_enabled", translate("Periodically check and remove deprecated IP?")) 104 | p:depends('enabled', '1') 105 | p = s:taboption("ip", ListValue, "day", translate("Day")) 106 | p:depends('check_enabled', '1') 107 | local weekdays = {{'Sun', 'Sunday'}, 108 | {'Mon', 'Monday'}, 109 | {'Tue', 'Tuesday'}, 110 | {'Wed', 'Wednesday'}, 111 | {'Thu', 'Thursday'}, 112 | {'Fri', 'Friday'}, 113 | {'Sat', 'Saturday'}, 114 | {'Every', 'Everyday'}} 115 | for _, d in ipairs(weekdays) do 116 | p:value(d[1], translate(d[2])) 117 | end 118 | p = s:taboption("ip", ListValue, "time", translate("Time")) 119 | p:depends('check_enabled', '1') 120 | for i = 0, 23 do 121 | p:value("%02d" % {i}, tostring(i)) 122 | end 123 | 124 | ---***--- 125 | s = m:section(NamedSection, "google", "oauth_services", "Google", 126 | translate("You can register your own Google app and use its parameters here.")) 127 | s:tab("general", translate("General settings")) 128 | s:tab("ip", translate("Service IP addresses")) 129 | 130 | s:taboption("general", SerFlag, "enabled", translate("Enabled?")) 131 | 132 | p = s:taboption("general", Flag, "googleall", translate("Accept all Google accounts?"), 133 | translate("User can use any Google Account email to login.
")) 134 | p:depends('enabled', '1') 135 | 136 | p = s:taboption("general", DynamicList, "googleapps", translate("Accept these Google Apps domains"), 137 | translate("Ex. mbm.vn. User will login with @mbm.vn email.
\ 138 | Note: To limit to these domains, you have to uncheck the\ 139 | all Google account option above.")) 140 | p:depends('enabled', '1') 141 | 142 | p = s:taboption("general", Value, "app_id", "App ID/ Client ID", 143 | translate("The Client ID of your app registered in\ 144 | Google API Console")) 145 | p:depends('enabled', '1') 146 | p.default = '242929894222-3909mjqkmgcdo9ro6mr91aiod083g834.apps.googleusercontent.com' 147 | 148 | p = s:taboption("general", Value, "redirect_uri", "Redirect URI", 149 | translate("This URI has to be match the one you registered for your Google app.
\ 150 | Have to be HTTPS. Its domain/IP must be included in Service IP addresses list (next tab).")) 151 | p:depends('enabled', '1') 152 | p.default = 'https://kikiauth.appspot.com/google' 153 | 154 | p = s:taboption("ip", IPList, "ips", "Google IPs",translate("List of Google IPs used for the gateway to open the traffic correctly while using Google OAuth.")) 155 | p:depends('enabled', '1') 156 | p.default = {'accounts.l.google.com', 157 | 'accounts-cctld.l.google.com', 158 | 'clients.l.google.com', 159 | 'googlehosted.l.googleusercontent.com', 160 | 'ssl.gstatic.com', 161 | 'kikiauth.appspot.com'} 162 | 163 | p = s:taboption("ip", Flag, "check_enabled", translate("Periodically check and remove deprecated IP?")) 164 | p:depends('enabled', '1') 165 | 166 | p = s:taboption("ip", ListValue, "day", translate("Day")) 167 | p:depends('check_enabled', '1') 168 | for _, d in ipairs(weekdays) do 169 | p:value(d[1], translate(d[2])) 170 | end 171 | 172 | p = s:taboption("ip", ListValue, "time", translate("Time")) 173 | p:depends('check_enabled', '1') 174 | for i = 0, 23 do 175 | p:value("%02d" % {i}, tostring(i)) 176 | end 177 | 178 | 179 | --[[ 180 | s = m:section(NamedSection, "twitter", "oauth_services", "Twitter") 181 | s:option(Flag, "enabled", translate("Enabled?")) 182 | p = s:option(Value, "app_id", "App ID/ Client ID") 183 | p:depends('enabled', '1') 184 | p = s:option(Value, "redirect_uri", "Redirect URI") 185 | p:depends('enabled', '1') 186 | --]] 187 | 188 | s = m:section(NamedSection, "oauth_success_page", "success_page", "Success page", 189 | translate("You can set a default success page which users will be redirected to after logging in successfully. Or, you can just display some welcome text to these users; but notice that this text is only showed if you do not provide the 'Success URL' field a value.")) 190 | p = s:option(Value, "success_url", "Success URL") 191 | p = s:option(TextValue, "success_text", "Success Text",translate("This is only displayed if you leave the 'Success URL' field blank. HTML tags can be used here.")) 192 | p.rows = 6 193 | 194 | return m -------------------------------------------------------------------------------- /luasrc/model/cbi/kikiauth-admin/status.lua: -------------------------------------------------------------------------------- 1 | authserver = require "luci.controller.kikiauth.authserver" 2 | local class = luci.util.class 3 | 4 | ApButton = class(Button) 5 | function ApButton.set_state(self, st) 6 | if st == "apply" then 7 | self.inputtitle = translate("Apply") 8 | self.inputstyle = 'apply' 9 | else 10 | self.inputtitle = translate("Remove") 11 | self.inputstyle = 'remove' 12 | end 13 | end 14 | 15 | 16 | f = SimpleForm("status", translate("Status")) 17 | f.reset = false 18 | f.submit = false 19 | s = f:section(SimpleSection) 20 | 21 | o = s:option(ApButton, "open_access", 22 | translate("Open access to OAuth services (default is blocked by WifiDog)")) 23 | 24 | if authserver.iptables_kikiauth_chain_exist() then 25 | o:set_state("remove") 26 | else 27 | o:set_state("apply") 28 | end 29 | -- Functions for the button 30 | function o.write(self, section) 31 | if self.inputstyle == 'apply' then 32 | -- Create iptables chain 33 | authserver.iptables_kikiauth_delete_chain() 34 | local r = authserver.iptables_kikiauth_create_chain() 35 | -- Get IPs to add to iptables chain 36 | local services = authserver.get_enabled_OAuth_service_list() 37 | local ips = {} 38 | for _, serv in ipairs(services) do 39 | local combine = luci.util.combine(ips, authserver.get_oauth_ip_list(serv)) 40 | -- Avoid to use nil table. 41 | if combine ~= nil then ips = combine end 42 | end 43 | -- Add rule to iptables 44 | for _, ip in ipairs(ips) do 45 | authserver.iptables_kikiauth_add_iprule(ip) 46 | end 47 | -- Insert KikiAuth rule to WifiDog chain 48 | authserver.iptables_kikiauth_insert_to_wifidog() 49 | if r then self:set_state('remove') end 50 | else 51 | if authserver.iptables_kikiauth_delete_chain() then self:set_state('apply') end 52 | end 53 | end 54 | 55 | return f -------------------------------------------------------------------------------- /luasrc/view/kikiauth/auth.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 41 |
42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /luasrc/view/kikiauth/facebookcallback.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | Client-side OAuth Example 4 | 26 | 27 | 28 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /luasrc/view/kikiauth/gatewaymessage.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | Gateway message 4 | 5 | 6 | 12 |

Sorry, you are unauthenticated.

13 |

There was error in authentication process.
It may be:

14 | 22 | 23 | -------------------------------------------------------------------------------- /luasrc/view/kikiauth/googlecallback.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /luasrc/view/kikiauth/login.htm: -------------------------------------------------------------------------------- 1 | <% 2 | local uci = luci.model.uci.cursor() 3 | local enabled_services = {} 4 | local fb_app_id, fb_redirect_uri, gg_app_id, gg_redirect_uri 5 | local gapps_domains = nil 6 | function check_service_enabled(sect) 7 | if sect.enabled == '1' then 8 | local name = sect['.name'] 9 | if name == 'facebook' then 10 | fb_app_id = sect.app_id 11 | fb_redirect_uri = sect.redirect_uri 12 | elseif name == 'google' then 13 | gg_app_id = sect.app_id 14 | gg_redirect_uri = sect.redirect_uri 15 | end 16 | table.insert(enabled_services, name) 17 | end 18 | end 19 | 20 | uci:foreach("kikiauth", "oauth_services", check_service_enabled) 21 | -- Add Google Apps domain 22 | if luci.util.contains(enabled_services, 'google') then 23 | local googleall = uci:get_bool("kikiauth", "google", "googleall") 24 | if not googleall then 25 | -- If googleall option is NO, we get Google Apps domains 26 | gapps_domains = uci:get_list("kikiauth", "google", "googleapps") 27 | end 28 | end 29 | %> 30 | 31 | 32 | Please choose one service to login 33 | 52 | 65 | 66 | 114 | 115 | 116 |
117 |
118 | <% 119 | local g = "" 120 | local f = "" 121 | for i, n in ipairs(enabled_services) do 122 | if n == 'facebook' then 123 | write(f) 124 | elseif n == 'google' and gapps_domains == nil then 125 | write(g) 126 | elseif gapps_domains ~= nil then 127 | %> 128 |
129 |
130 | Login with Google Apps domain: 131 |
    132 | <% 133 | for _, d in ipairs(gapps_domains) do 134 | write("
  • " 135 | % {d, d}) 136 | end 137 | %> 138 |
139 |
140 | <% 141 | end 142 | end 143 | %> 144 |
145 | 146 | 147 | --------------------------------------------------------------------------------