From daa1b5684b1f1c751a450a3e9b669cace9c6984a Mon Sep 17 00:00:00 2001 From: JamesonHuang <462430725@qq.com> Date: Sat, 9 May 2015 18:48:46 +0800 Subject: [PATCH] create luci frame --- Me_Lua/r13/MZLog.lua | 14 + Me_Lua/r13/Posix.lua | 113 + Me_Lua/r13/cjson.so | Bin 0 -> 37404 bytes Me_Lua/r13/lfs.so | Bin 0 -> 20493 bytes Me_Lua/r13/lsqlite3.so | Bin 0 -> 53091 bytes Me_Lua/r13/ltn12.lua | 305 ++ Me_Lua/r13/luci/controller/admin/network.lua | 611 ++++ Me_Lua/r13/luci/controller/api/index.lua | 238 ++ Me_Lua/r13/luci/controller/bs/index.lua | 105 + Me_Lua/r13/luci/dispatcher.lua | 965 ++++++ Me_Lua/r13/luci/model/network.lua | 1632 ++++++++++ Me_Lua/r13/luci/sauth.lua | 200 ++ Me_Lua/r13/meizu/arpmon.lua | 103 + Me_Lua/r13/meizu/bfs.lua | 370 +++ Me_Lua/r13/meizu/build.sh | 5 + Me_Lua/r13/meizu/dbfs.lua | 410 +++ Me_Lua/r13/meizu/nwfs.lua | 2804 +++++++++++++++++ Me_Lua/r13/meizu/r10config.lua | 3 + Me_Lua/r13/meizu/sipfs.lua | 690 ++++ Me_Lua/r13/meizu/upgdfs.lua | 231 ++ Me_Lua/r13/mime.lua | 90 + Me_Lua/r13/mime.so.1.0.3 | Bin 0 -> 10200 bytes Me_Lua/r13/mime/core.so | Bin 0 -> 86651 bytes Me_Lua/r13/posix.so | Bin 0 -> 45066 bytes Me_Lua/r13/routerReport.lua | 136 + Me_Lua/r13/socket.lua | 149 + Me_Lua/r13/socket.so.3.0-rc1 | Bin 0 -> 36276 bytes Me_Lua/r13/socket/core.so | Bin 0 -> 306460 bytes Me_Lua/r13/socket/ftp.lua | 285 ++ Me_Lua/r13/socket/headers.lua | 104 + Me_Lua/r13/socket/http.lua | 356 +++ Me_Lua/r13/socket/smtp.lua | 256 ++ Me_Lua/r13/socket/tp.lua | 126 + Me_Lua/r13/socket/url.lua | 307 ++ Me_Lua/r13/ssl.lua | 168 + Me_Lua/r13/ssl.so | Bin 0 -> 67881 bytes Me_Lua/r13/ssl/https.lua | 138 + Me_Lua/r13/tags | 669 ++++ Mi_Lua/Posix.lua | 113 + Mi_Lua/bit.lua | 15 + Mi_Lua/cjson.so | Bin 0 -> 19332 bytes Mi_Lua/iproute.so | Bin 0 -> 23408 bytes Mi_Lua/iwinfo.so | Bin 0 -> 18840 bytes Mi_Lua/json.lua | 376 +++ Mi_Lua/logging.lua | 201 ++ Mi_Lua/logging/console.lua | 20 + Mi_Lua/logging/email.lua | 43 + Mi_Lua/logging/file.lua | 49 + Mi_Lua/logging/rolling_file.lua | 79 + Mi_Lua/logging/socket.lua | 33 + Mi_Lua/logging/sql.lua | 62 + Mi_Lua/lsqlite3.so | Bin 0 -> 27456 bytes Mi_Lua/ltn12.lua | 292 ++ Mi_Lua/luci/cacheloader.lua | 23 + Mi_Lua/luci/cbi.lua | 1850 +++++++++++ Mi_Lua/luci/cbi/datatypes.lua | 345 ++ Mi_Lua/luci/ccache.lua | 87 + Mi_Lua/luci/config.lua | 42 + Mi_Lua/luci/controller/api/index.lua | 10 + Mi_Lua/luci/controller/api/xqdatacenter.lua | 336 ++ Mi_Lua/luci/controller/api/xqnetdetect.lua | 293 ++ Mi_Lua/luci/controller/api/xqnetwork.lua | 1310 ++++++++ Mi_Lua/luci/controller/api/xqpassport.lua | 316 ++ Mi_Lua/luci/controller/api/xqsmarthome.lua | 67 + Mi_Lua/luci/controller/api/xqsystem.lua | 1598 ++++++++++ Mi_Lua/luci/controller/api/xqtunnel.lua | 53 + Mi_Lua/luci/controller/dispatch/index.lua | 18 + Mi_Lua/luci/controller/firewall.lua | 23 + Mi_Lua/luci/controller/mobile/index.lua | 32 + Mi_Lua/luci/controller/service/datacenter.lua | 382 +++ Mi_Lua/luci/controller/service/index.lua | 10 + Mi_Lua/luci/controller/web/index.lua | 96 + Mi_Lua/luci/datacentertunnel.so | Bin 0 -> 174576 bytes Mi_Lua/luci/debug.lua | 37 + Mi_Lua/luci/dispatcher.lua | 1037 ++++++ Mi_Lua/luci/fs.lua | 244 ++ Mi_Lua/luci/http.lua | 336 ++ Mi_Lua/luci/http/protocol.lua | 727 +++++ Mi_Lua/luci/http/protocol/conditionals.lua | 153 + Mi_Lua/luci/http/protocol/date.lua | 115 + Mi_Lua/luci/http/protocol/mime.lua | 99 + Mi_Lua/luci/i18n.lua | 104 + Mi_Lua/luci/i18n/base.en.lmo | Bin 0 -> 64 bytes Mi_Lua/luci/i18n/base.zh-cn.lmo | Bin 0 -> 72 bytes Mi_Lua/luci/i18n/firewall.ca.lmo | Bin 0 -> 716 bytes Mi_Lua/luci/i18n/firewall.cs.lmo | Bin 0 -> 7108 bytes Mi_Lua/luci/i18n/firewall.de.lmo | Bin 0 -> 7112 bytes Mi_Lua/luci/i18n/firewall.el.lmo | Bin 0 -> 2672 bytes Mi_Lua/luci/i18n/firewall.es.lmo | Bin 0 -> 7136 bytes Mi_Lua/luci/i18n/firewall.fr.lmo | Bin 0 -> 3144 bytes Mi_Lua/luci/i18n/firewall.hu.lmo | Bin 0 -> 6404 bytes Mi_Lua/luci/i18n/firewall.it.lmo | Bin 0 -> 1464 bytes Mi_Lua/luci/i18n/firewall.ja.lmo | Bin 0 -> 8836 bytes Mi_Lua/luci/i18n/firewall.no.lmo | Bin 0 -> 2896 bytes Mi_Lua/luci/i18n/firewall.pl.lmo | Bin 0 -> 7160 bytes Mi_Lua/luci/i18n/firewall.pt-br.lmo | Bin 0 -> 7332 bytes Mi_Lua/luci/i18n/firewall.pt.lmo | Bin 0 -> 724 bytes Mi_Lua/luci/i18n/firewall.ro.lmo | Bin 0 -> 1272 bytes Mi_Lua/luci/i18n/firewall.ru.lmo | Bin 0 -> 11268 bytes Mi_Lua/luci/i18n/firewall.uk.lmo | Bin 0 -> 10800 bytes Mi_Lua/luci/i18n/firewall.vi.lmo | Bin 0 -> 652 bytes Mi_Lua/luci/i18n/firewall.zh-cn.lmo | Bin 0 -> 5968 bytes Mi_Lua/luci/i18n/firewall.zh-tw.lmo | Bin 0 -> 5968 bytes Mi_Lua/luci/init.lua | 39 + Mi_Lua/luci/ip.lua | 684 ++++ Mi_Lua/luci/json.lua | 566 ++++ Mi_Lua/luci/ltn12.lua | 391 +++ .../model/cbi/admin_network/proto_dhcp.lua | 88 + .../model/cbi/admin_network/proto_l2tp.lua | 69 + .../model/cbi/admin_network/proto_none.lua | 13 + .../model/cbi/admin_network/proto_ppp.lua | 136 + .../model/cbi/admin_network/proto_pppoa.lua | 142 + .../model/cbi/admin_network/proto_pppoe.lua | 136 + .../model/cbi/admin_network/proto_pptp.lua | 116 + .../model/cbi/admin_network/proto_static.lua | 83 + Mi_Lua/luci/model/cbi/firewall/custom.lua | 38 + .../model/cbi/firewall/forward-details.lua | 178 ++ Mi_Lua/luci/model/cbi/firewall/forwards.lua | 144 + .../luci/model/cbi/firewall/rule-details.lua | 338 ++ Mi_Lua/luci/model/cbi/firewall/rules.lua | 269 ++ .../luci/model/cbi/firewall/zone-details.lua | 243 ++ Mi_Lua/luci/model/cbi/firewall/zones.lua | 88 + Mi_Lua/luci/model/firewall.lua | 582 ++++ Mi_Lua/luci/model/ipkg.lua | 239 ++ Mi_Lua/luci/model/network.lua | 1639 ++++++++++ Mi_Lua/luci/model/network/proto_ppp.lua | 104 + Mi_Lua/luci/model/uci.lua | 404 +++ Mi_Lua/luci/sauth.lua | 165 + Mi_Lua/luci/sgi/cgi.lua | 95 + Mi_Lua/luci/sgi/uhttpd.lua | 121 + Mi_Lua/luci/store.lua | 16 + Mi_Lua/luci/sys.lua | 966 ++++++ Mi_Lua/luci/sys/iptparser.lua | 371 +++ Mi_Lua/luci/sys/zoneinfo.lua | 28 + Mi_Lua/luci/sys/zoneinfo/tzdata.lua | 420 +++ Mi_Lua/luci/sys/zoneinfo/tzoffset.lua | 162 + Mi_Lua/luci/template.lua | 107 + Mi_Lua/luci/template/parser.so | Bin 0 -> 14188 bytes Mi_Lua/luci/tools/firewall.lua | 291 ++ Mi_Lua/luci/tools/proto.lua | 46 + Mi_Lua/luci/tools/status.lua | 216 ++ Mi_Lua/luci/tools/webadmin.lua | 173 + Mi_Lua/luci/util.lua | 855 +++++ Mi_Lua/luci/version.lua | 14 + Mi_Lua/luci/view/error404.htm | 19 + Mi_Lua/luci/view/error500.htm | 19 + Mi_Lua/luci/view/firewall/cbi_addforward.htm | 115 + Mi_Lua/luci/view/firewall/cbi_addrule.htm | 112 + Mi_Lua/luci/view/firewall/cbi_addsnat.htm | 66 + Mi_Lua/luci/view/footer.htm | 15 + Mi_Lua/luci/view/header.htm | 21 + Mi_Lua/luci/view/index.htm | 22 + Mi_Lua/luci/view/indexer.htm | 15 + Mi_Lua/luci/view/mobile/home.htm | 23 + Mi_Lua/luci/view/mobile/inc/footer.htm | 30 + Mi_Lua/luci/view/mobile/inc/header.htm | 3 + Mi_Lua/luci/view/mobile/init/agreement.htm | 26 + Mi_Lua/luci/view/mobile/init/guide.htm | 340 ++ Mi_Lua/luci/view/mobile/init/hello.htm | 111 + Mi_Lua/luci/view/mobile/sysauth.htm | 51 + .../luci/view/themes/openwrt.org/footer.htm | 21 + .../luci/view/themes/openwrt.org/header.htm | 186 ++ Mi_Lua/luci/view/themes/xiaoqiang/footer.htm | 0 Mi_Lua/luci/view/themes/xiaoqiang/header.htm | 3 + Mi_Lua/luci/view/web/application.htm | 27 + Mi_Lua/luci/view/web/gateway.htm | 130 + Mi_Lua/luci/view/web/gateway.html | 61 + Mi_Lua/luci/view/web/inc/agreement.htm | 558 ++++ Mi_Lua/luci/view/web/inc/footer.htm | 18 + Mi_Lua/luci/view/web/inc/footermini.htm | 3 + Mi_Lua/luci/view/web/inc/g.js.base.htm | 231 ++ Mi_Lua/luci/view/web/inc/g.js.htm | 161 + Mi_Lua/luci/view/web/inc/head.htm | 12 + Mi_Lua/luci/view/web/inc/header.htm | 20 + Mi_Lua/luci/view/web/inc/nav.htm | 16 + Mi_Lua/luci/view/web/inc/reboot.js.htm | 119 + Mi_Lua/luci/view/web/inc/upgrade.js.htm | 159 + Mi_Lua/luci/view/web/index.htm | 412 +++ Mi_Lua/luci/view/web/init/agreement.htm | 27 + Mi_Lua/luci/view/web/init/guide.htm | 442 +++ Mi_Lua/luci/view/web/init/hello.htm | 80 + Mi_Lua/luci/view/web/init/webinitrdr.htm | 204 ++ Mi_Lua/luci/view/web/manager.htm | 425 +++ Mi_Lua/luci/view/web/netdetection.htm | 251 ++ Mi_Lua/luci/view/web/netset.htm | 210 ++ Mi_Lua/luci/view/web/plugin.htm | 661 ++++ Mi_Lua/luci/view/web/plugins/guest.htm | 178 ++ Mi_Lua/luci/view/web/plugins/kuaipan.htm | 521 +++ Mi_Lua/luci/view/web/plugins/xunlei.htm | 22 + Mi_Lua/luci/view/web/safeurl.htm | 16 + Mi_Lua/luci/view/web/setting.htm | 290 ++ Mi_Lua/luci/view/web/setting/ddns.htm | 409 +++ Mi_Lua/luci/view/web/setting/developer.htm | 165 + Mi_Lua/luci/view/web/setting/diskformat.htm | 70 + Mi_Lua/luci/view/web/setting/dmz.htm | 236 ++ Mi_Lua/luci/view/web/setting/lamp.htm | 156 + Mi_Lua/luci/view/web/setting/log.htm | 54 + Mi_Lua/luci/view/web/setting/nat.htm | 592 ++++ .../luci/view/web/setting/net_ipmacband.htm | 317 ++ Mi_Lua/luci/view/web/setting/net_lan.htm | 310 ++ .../luci/view/web/setting/net_setup_mac.htm | 134 + Mi_Lua/luci/view/web/setting/net_wan.htm | 576 ++++ Mi_Lua/luci/view/web/setting/nginx.htm | 104 + Mi_Lua/luci/view/web/setting/noflushd.htm | 75 + Mi_Lua/luci/view/web/setting/passport.htm | 143 + Mi_Lua/luci/view/web/setting/predownload.htm | 74 + Mi_Lua/luci/view/web/setting/qos.htm | 233 ++ Mi_Lua/luci/view/web/setting/qos_pro.htm | 606 ++++ Mi_Lua/luci/view/web/setting/reboot.htm | 50 + Mi_Lua/luci/view/web/setting/reset.htm | 40 + Mi_Lua/luci/view/web/setting/sys_psp.htm | 196 ++ Mi_Lua/luci/view/web/setting/sys_status.htm | 113 + Mi_Lua/luci/view/web/setting/upgrade.htm | 76 + .../luci/view/web/setting/upgrade_manual.htm | 139 + .../luci/view/web/setting/upload_config.htm | 42 + Mi_Lua/luci/view/web/setting/upnp.htm | 96 + Mi_Lua/luci/view/web/setting/vpn.htm | 228 ++ Mi_Lua/luci/view/web/setting/wifi_filter.htm | 395 +++ Mi_Lua/luci/view/web/setting/wifi_set.htm | 544 ++++ .../luci/view/web/setting/wifi_set_mini.htm | 324 ++ Mi_Lua/luci/view/web/setting/wifi_set_pro.htm | 441 +++ .../view/web/setting/wifi_setup_channel.htm | 187 ++ Mi_Lua/luci/view/web/setting/wifi_txpwr.htm | 102 + Mi_Lua/luci/view/web/sysauth.htm | 309 ++ Mi_Lua/luci/view/web/syslock.htm | 78 + Mi_Lua/luci/view/web/sysset.htm | 287 ++ Mi_Lua/luci/view/web/urldetection.htm | 88 + Mi_Lua/luci/view/web/xmaccount.htm | 45 + Mi_Lua/mime.lua | 87 + Mi_Lua/mime.so.1.0.2 | Bin 0 -> 11068 bytes Mi_Lua/net_tools.so | Bin 0 -> 18800 bytes Mi_Lua/nixio.so | Bin 0 -> 69136 bytes Mi_Lua/nixio/fs.lua | 175 + Mi_Lua/nixio/util.lua | 270 ++ Mi_Lua/posix.so | Bin 0 -> 41756 bytes Mi_Lua/rc4.lua | 77 + Mi_Lua/service/util/ServiceErrorUtil.lua | 24 + Mi_Lua/sha1.lua | 718 +++++ Mi_Lua/slaxdom.lua | 53 + Mi_Lua/slaxml.lua | 221 ++ Mi_Lua/socket.lua | 133 + Mi_Lua/socket.so.2.0.2 | Bin 0 -> 35436 bytes Mi_Lua/socket/ftp.lua | 281 ++ Mi_Lua/socket/http.lua | 350 ++ Mi_Lua/socket/smtp.lua | 251 ++ Mi_Lua/socket/tp.lua | 123 + Mi_Lua/socket/url.lua | 297 ++ Mi_Lua/ssl.lua | 93 + Mi_Lua/ssl.so | Bin 0 -> 23144 bytes Mi_Lua/ssl/https.lua | 138 + Mi_Lua/sysapi/miqos.lua | 82 + Mi_Lua/traffic.lua | 63 + Mi_Lua/ubus.so | Bin 0 -> 12968 bytes Mi_Lua/uci.so | Bin 0 -> 13224 bytes Mi_Lua/xiaoqiang/XQCountryCode.lua | 45 + Mi_Lua/xiaoqiang/XQEquipment.lua | 209 ++ Mi_Lua/xiaoqiang/XQEvent.lua | 10 + Mi_Lua/xiaoqiang/XQLog.lua | 78 + Mi_Lua/xiaoqiang/XQPreference.lua | 32 + Mi_Lua/xiaoqiang/XQPushHelper.lua | 415 +++ Mi_Lua/xiaoqiang/XQVersion.lua | 10 + Mi_Lua/xiaoqiang/common/XQConfigs.lua | 220 ++ Mi_Lua/xiaoqiang/common/XQFunction.lua | 345 ++ Mi_Lua/xiaoqiang/module/XQAPModule.lua | 26 + Mi_Lua/xiaoqiang/module/XQDDNS.lua | 280 ++ Mi_Lua/xiaoqiang/module/XQDMZModule.lua | 275 ++ Mi_Lua/xiaoqiang/module/XQGuestWifi.lua | 197 ++ .../xiaoqiang/module/XQNetworkSpeedTest.lua | 94 + Mi_Lua/xiaoqiang/module/XQPortForward.lua | 266 ++ Mi_Lua/xiaoqiang/module/XQPredownload.lua | 35 + Mi_Lua/xiaoqiang/util/XQCacheUtil.lua | 43 + Mi_Lua/xiaoqiang/util/XQCameraUtil.lua | 358 +++ Mi_Lua/xiaoqiang/util/XQCryptoUtil.lua | 81 + Mi_Lua/xiaoqiang/util/XQDBUtil.lua | 219 ++ Mi_Lua/xiaoqiang/util/XQDeviceUtil.lua | 571 ++++ Mi_Lua/xiaoqiang/util/XQDownloadUtil.lua | 390 +++ Mi_Lua/xiaoqiang/util/XQErrorUtil.lua | 123 + Mi_Lua/xiaoqiang/util/XQHttpUtil.lua | 108 + Mi_Lua/xiaoqiang/util/XQLanWanUtil.lua | 962 ++++++ Mi_Lua/xiaoqiang/util/XQMitvUtil.lua | 165 + Mi_Lua/xiaoqiang/util/XQNetUtil.lua | 679 ++++ Mi_Lua/xiaoqiang/util/XQQoSUtil.lua | 314 ++ Mi_Lua/xiaoqiang/util/XQSDKUtil.lua | 62 + Mi_Lua/xiaoqiang/util/XQSecureUtil.lua | 346 ++ Mi_Lua/xiaoqiang/util/XQSysUtil.lua | 809 +++++ Mi_Lua/xiaoqiang/util/XQUPnPUtil.lua | 57 + Mi_Lua/xiaoqiang/util/XQVPNUtil.lua | 90 + Mi_Lua/xiaoqiang/util/XQWifiUtil.lua | 1206 +++++++ Mi_Lua/xiaoqiang/util/XQZigbeeUtil.lua | 65 + Mi_Lua/xqcrypto.so | Bin 0 -> 30016 bytes Mi_Lua/xssFilter.lua | 325 ++ OpenWrt命令记录.txt | 114 + 292 files changed, 60930 insertions(+) create mode 100644 Me_Lua/r13/MZLog.lua create mode 100644 Me_Lua/r13/Posix.lua create mode 100644 Me_Lua/r13/cjson.so create mode 100644 Me_Lua/r13/lfs.so create mode 100644 Me_Lua/r13/lsqlite3.so create mode 100644 Me_Lua/r13/ltn12.lua create mode 100644 Me_Lua/r13/luci/controller/admin/network.lua create mode 100644 Me_Lua/r13/luci/controller/api/index.lua create mode 100644 Me_Lua/r13/luci/controller/bs/index.lua create mode 100644 Me_Lua/r13/luci/dispatcher.lua create mode 100644 Me_Lua/r13/luci/model/network.lua create mode 100644 Me_Lua/r13/luci/sauth.lua create mode 100644 Me_Lua/r13/meizu/arpmon.lua create mode 100644 Me_Lua/r13/meizu/bfs.lua create mode 100644 Me_Lua/r13/meizu/build.sh create mode 100644 Me_Lua/r13/meizu/dbfs.lua create mode 100644 Me_Lua/r13/meizu/nwfs.lua create mode 100644 Me_Lua/r13/meizu/r10config.lua create mode 100644 Me_Lua/r13/meizu/sipfs.lua create mode 100644 Me_Lua/r13/meizu/upgdfs.lua create mode 100644 Me_Lua/r13/mime.lua create mode 100644 Me_Lua/r13/mime.so.1.0.3 create mode 100644 Me_Lua/r13/mime/core.so create mode 100644 Me_Lua/r13/posix.so create mode 100644 Me_Lua/r13/routerReport.lua create mode 100644 Me_Lua/r13/socket.lua create mode 100644 Me_Lua/r13/socket.so.3.0-rc1 create mode 100644 Me_Lua/r13/socket/core.so create mode 100644 Me_Lua/r13/socket/ftp.lua create mode 100644 Me_Lua/r13/socket/headers.lua create mode 100644 Me_Lua/r13/socket/http.lua create mode 100644 Me_Lua/r13/socket/smtp.lua create mode 100644 Me_Lua/r13/socket/tp.lua create mode 100644 Me_Lua/r13/socket/url.lua create mode 100644 Me_Lua/r13/ssl.lua create mode 100644 Me_Lua/r13/ssl.so create mode 100644 Me_Lua/r13/ssl/https.lua create mode 100644 Me_Lua/r13/tags create mode 100644 Mi_Lua/Posix.lua create mode 100644 Mi_Lua/bit.lua create mode 100644 Mi_Lua/cjson.so create mode 100644 Mi_Lua/iproute.so create mode 100644 Mi_Lua/iwinfo.so create mode 100644 Mi_Lua/json.lua create mode 100644 Mi_Lua/logging.lua create mode 100644 Mi_Lua/logging/console.lua create mode 100644 Mi_Lua/logging/email.lua create mode 100644 Mi_Lua/logging/file.lua create mode 100644 Mi_Lua/logging/rolling_file.lua create mode 100644 Mi_Lua/logging/socket.lua create mode 100644 Mi_Lua/logging/sql.lua create mode 100644 Mi_Lua/lsqlite3.so create mode 100644 Mi_Lua/ltn12.lua create mode 100644 Mi_Lua/luci/cacheloader.lua create mode 100644 Mi_Lua/luci/cbi.lua create mode 100644 Mi_Lua/luci/cbi/datatypes.lua create mode 100644 Mi_Lua/luci/ccache.lua create mode 100644 Mi_Lua/luci/config.lua create mode 100644 Mi_Lua/luci/controller/api/index.lua create mode 100644 Mi_Lua/luci/controller/api/xqdatacenter.lua create mode 100644 Mi_Lua/luci/controller/api/xqnetdetect.lua create mode 100644 Mi_Lua/luci/controller/api/xqnetwork.lua create mode 100644 Mi_Lua/luci/controller/api/xqpassport.lua create mode 100644 Mi_Lua/luci/controller/api/xqsmarthome.lua create mode 100644 Mi_Lua/luci/controller/api/xqsystem.lua create mode 100644 Mi_Lua/luci/controller/api/xqtunnel.lua create mode 100644 Mi_Lua/luci/controller/dispatch/index.lua create mode 100644 Mi_Lua/luci/controller/firewall.lua create mode 100644 Mi_Lua/luci/controller/mobile/index.lua create mode 100644 Mi_Lua/luci/controller/service/datacenter.lua create mode 100644 Mi_Lua/luci/controller/service/index.lua create mode 100644 Mi_Lua/luci/controller/web/index.lua create mode 100644 Mi_Lua/luci/datacentertunnel.so create mode 100644 Mi_Lua/luci/debug.lua create mode 100644 Mi_Lua/luci/dispatcher.lua create mode 100644 Mi_Lua/luci/fs.lua create mode 100644 Mi_Lua/luci/http.lua create mode 100644 Mi_Lua/luci/http/protocol.lua create mode 100644 Mi_Lua/luci/http/protocol/conditionals.lua create mode 100644 Mi_Lua/luci/http/protocol/date.lua create mode 100644 Mi_Lua/luci/http/protocol/mime.lua create mode 100644 Mi_Lua/luci/i18n.lua create mode 100644 Mi_Lua/luci/i18n/base.en.lmo create mode 100644 Mi_Lua/luci/i18n/base.zh-cn.lmo create mode 100644 Mi_Lua/luci/i18n/firewall.ca.lmo create mode 100644 Mi_Lua/luci/i18n/firewall.cs.lmo create mode 100644 Mi_Lua/luci/i18n/firewall.de.lmo create mode 100644 Mi_Lua/luci/i18n/firewall.el.lmo create mode 100644 Mi_Lua/luci/i18n/firewall.es.lmo create mode 100644 Mi_Lua/luci/i18n/firewall.fr.lmo create mode 100644 Mi_Lua/luci/i18n/firewall.hu.lmo create mode 100644 Mi_Lua/luci/i18n/firewall.it.lmo create mode 100644 Mi_Lua/luci/i18n/firewall.ja.lmo create mode 100644 Mi_Lua/luci/i18n/firewall.no.lmo create mode 100644 Mi_Lua/luci/i18n/firewall.pl.lmo create mode 100644 Mi_Lua/luci/i18n/firewall.pt-br.lmo create mode 100644 Mi_Lua/luci/i18n/firewall.pt.lmo create mode 100644 Mi_Lua/luci/i18n/firewall.ro.lmo create mode 100644 Mi_Lua/luci/i18n/firewall.ru.lmo create mode 100644 Mi_Lua/luci/i18n/firewall.uk.lmo create mode 100644 Mi_Lua/luci/i18n/firewall.vi.lmo create mode 100644 Mi_Lua/luci/i18n/firewall.zh-cn.lmo create mode 100644 Mi_Lua/luci/i18n/firewall.zh-tw.lmo create mode 100644 Mi_Lua/luci/init.lua create mode 100644 Mi_Lua/luci/ip.lua create mode 100644 Mi_Lua/luci/json.lua create mode 100644 Mi_Lua/luci/ltn12.lua create mode 100644 Mi_Lua/luci/model/cbi/admin_network/proto_dhcp.lua create mode 100644 Mi_Lua/luci/model/cbi/admin_network/proto_l2tp.lua create mode 100644 Mi_Lua/luci/model/cbi/admin_network/proto_none.lua create mode 100644 Mi_Lua/luci/model/cbi/admin_network/proto_ppp.lua create mode 100644 Mi_Lua/luci/model/cbi/admin_network/proto_pppoa.lua create mode 100644 Mi_Lua/luci/model/cbi/admin_network/proto_pppoe.lua create mode 100644 Mi_Lua/luci/model/cbi/admin_network/proto_pptp.lua create mode 100644 Mi_Lua/luci/model/cbi/admin_network/proto_static.lua create mode 100644 Mi_Lua/luci/model/cbi/firewall/custom.lua create mode 100644 Mi_Lua/luci/model/cbi/firewall/forward-details.lua create mode 100644 Mi_Lua/luci/model/cbi/firewall/forwards.lua create mode 100644 Mi_Lua/luci/model/cbi/firewall/rule-details.lua create mode 100644 Mi_Lua/luci/model/cbi/firewall/rules.lua create mode 100644 Mi_Lua/luci/model/cbi/firewall/zone-details.lua create mode 100644 Mi_Lua/luci/model/cbi/firewall/zones.lua create mode 100644 Mi_Lua/luci/model/firewall.lua create mode 100644 Mi_Lua/luci/model/ipkg.lua create mode 100644 Mi_Lua/luci/model/network.lua create mode 100644 Mi_Lua/luci/model/network/proto_ppp.lua create mode 100644 Mi_Lua/luci/model/uci.lua create mode 100644 Mi_Lua/luci/sauth.lua create mode 100644 Mi_Lua/luci/sgi/cgi.lua create mode 100644 Mi_Lua/luci/sgi/uhttpd.lua create mode 100644 Mi_Lua/luci/store.lua create mode 100644 Mi_Lua/luci/sys.lua create mode 100644 Mi_Lua/luci/sys/iptparser.lua create mode 100644 Mi_Lua/luci/sys/zoneinfo.lua create mode 100644 Mi_Lua/luci/sys/zoneinfo/tzdata.lua create mode 100644 Mi_Lua/luci/sys/zoneinfo/tzoffset.lua create mode 100644 Mi_Lua/luci/template.lua create mode 100644 Mi_Lua/luci/template/parser.so create mode 100644 Mi_Lua/luci/tools/firewall.lua create mode 100644 Mi_Lua/luci/tools/proto.lua create mode 100644 Mi_Lua/luci/tools/status.lua create mode 100644 Mi_Lua/luci/tools/webadmin.lua create mode 100644 Mi_Lua/luci/util.lua create mode 100644 Mi_Lua/luci/version.lua create mode 100644 Mi_Lua/luci/view/error404.htm create mode 100644 Mi_Lua/luci/view/error500.htm create mode 100644 Mi_Lua/luci/view/firewall/cbi_addforward.htm create mode 100644 Mi_Lua/luci/view/firewall/cbi_addrule.htm create mode 100644 Mi_Lua/luci/view/firewall/cbi_addsnat.htm create mode 100644 Mi_Lua/luci/view/footer.htm create mode 100644 Mi_Lua/luci/view/header.htm create mode 100644 Mi_Lua/luci/view/index.htm create mode 100644 Mi_Lua/luci/view/indexer.htm create mode 100644 Mi_Lua/luci/view/mobile/home.htm create mode 100644 Mi_Lua/luci/view/mobile/inc/footer.htm create mode 100644 Mi_Lua/luci/view/mobile/inc/header.htm create mode 100644 Mi_Lua/luci/view/mobile/init/agreement.htm create mode 100644 Mi_Lua/luci/view/mobile/init/guide.htm create mode 100644 Mi_Lua/luci/view/mobile/init/hello.htm create mode 100644 Mi_Lua/luci/view/mobile/sysauth.htm create mode 100644 Mi_Lua/luci/view/themes/openwrt.org/footer.htm create mode 100644 Mi_Lua/luci/view/themes/openwrt.org/header.htm create mode 100644 Mi_Lua/luci/view/themes/xiaoqiang/footer.htm create mode 100644 Mi_Lua/luci/view/themes/xiaoqiang/header.htm create mode 100644 Mi_Lua/luci/view/web/application.htm create mode 100644 Mi_Lua/luci/view/web/gateway.htm create mode 100644 Mi_Lua/luci/view/web/gateway.html create mode 100644 Mi_Lua/luci/view/web/inc/agreement.htm create mode 100644 Mi_Lua/luci/view/web/inc/footer.htm create mode 100644 Mi_Lua/luci/view/web/inc/footermini.htm create mode 100644 Mi_Lua/luci/view/web/inc/g.js.base.htm create mode 100644 Mi_Lua/luci/view/web/inc/g.js.htm create mode 100644 Mi_Lua/luci/view/web/inc/head.htm create mode 100644 Mi_Lua/luci/view/web/inc/header.htm create mode 100644 Mi_Lua/luci/view/web/inc/nav.htm create mode 100644 Mi_Lua/luci/view/web/inc/reboot.js.htm create mode 100644 Mi_Lua/luci/view/web/inc/upgrade.js.htm create mode 100644 Mi_Lua/luci/view/web/index.htm create mode 100644 Mi_Lua/luci/view/web/init/agreement.htm create mode 100644 Mi_Lua/luci/view/web/init/guide.htm create mode 100644 Mi_Lua/luci/view/web/init/hello.htm create mode 100644 Mi_Lua/luci/view/web/init/webinitrdr.htm create mode 100644 Mi_Lua/luci/view/web/manager.htm create mode 100644 Mi_Lua/luci/view/web/netdetection.htm create mode 100644 Mi_Lua/luci/view/web/netset.htm create mode 100644 Mi_Lua/luci/view/web/plugin.htm create mode 100644 Mi_Lua/luci/view/web/plugins/guest.htm create mode 100644 Mi_Lua/luci/view/web/plugins/kuaipan.htm create mode 100644 Mi_Lua/luci/view/web/plugins/xunlei.htm create mode 100644 Mi_Lua/luci/view/web/safeurl.htm create mode 100644 Mi_Lua/luci/view/web/setting.htm create mode 100644 Mi_Lua/luci/view/web/setting/ddns.htm create mode 100644 Mi_Lua/luci/view/web/setting/developer.htm create mode 100644 Mi_Lua/luci/view/web/setting/diskformat.htm create mode 100644 Mi_Lua/luci/view/web/setting/dmz.htm create mode 100644 Mi_Lua/luci/view/web/setting/lamp.htm create mode 100644 Mi_Lua/luci/view/web/setting/log.htm create mode 100644 Mi_Lua/luci/view/web/setting/nat.htm create mode 100644 Mi_Lua/luci/view/web/setting/net_ipmacband.htm create mode 100644 Mi_Lua/luci/view/web/setting/net_lan.htm create mode 100644 Mi_Lua/luci/view/web/setting/net_setup_mac.htm create mode 100644 Mi_Lua/luci/view/web/setting/net_wan.htm create mode 100644 Mi_Lua/luci/view/web/setting/nginx.htm create mode 100644 Mi_Lua/luci/view/web/setting/noflushd.htm create mode 100644 Mi_Lua/luci/view/web/setting/passport.htm create mode 100644 Mi_Lua/luci/view/web/setting/predownload.htm create mode 100644 Mi_Lua/luci/view/web/setting/qos.htm create mode 100644 Mi_Lua/luci/view/web/setting/qos_pro.htm create mode 100644 Mi_Lua/luci/view/web/setting/reboot.htm create mode 100644 Mi_Lua/luci/view/web/setting/reset.htm create mode 100644 Mi_Lua/luci/view/web/setting/sys_psp.htm create mode 100644 Mi_Lua/luci/view/web/setting/sys_status.htm create mode 100644 Mi_Lua/luci/view/web/setting/upgrade.htm create mode 100644 Mi_Lua/luci/view/web/setting/upgrade_manual.htm create mode 100644 Mi_Lua/luci/view/web/setting/upload_config.htm create mode 100644 Mi_Lua/luci/view/web/setting/upnp.htm create mode 100644 Mi_Lua/luci/view/web/setting/vpn.htm create mode 100644 Mi_Lua/luci/view/web/setting/wifi_filter.htm create mode 100644 Mi_Lua/luci/view/web/setting/wifi_set.htm create mode 100644 Mi_Lua/luci/view/web/setting/wifi_set_mini.htm create mode 100644 Mi_Lua/luci/view/web/setting/wifi_set_pro.htm create mode 100644 Mi_Lua/luci/view/web/setting/wifi_setup_channel.htm create mode 100644 Mi_Lua/luci/view/web/setting/wifi_txpwr.htm create mode 100644 Mi_Lua/luci/view/web/sysauth.htm create mode 100644 Mi_Lua/luci/view/web/syslock.htm create mode 100644 Mi_Lua/luci/view/web/sysset.htm create mode 100644 Mi_Lua/luci/view/web/urldetection.htm create mode 100644 Mi_Lua/luci/view/web/xmaccount.htm create mode 100644 Mi_Lua/mime.lua create mode 100644 Mi_Lua/mime.so.1.0.2 create mode 100644 Mi_Lua/net_tools.so create mode 100644 Mi_Lua/nixio.so create mode 100644 Mi_Lua/nixio/fs.lua create mode 100644 Mi_Lua/nixio/util.lua create mode 100644 Mi_Lua/posix.so create mode 100644 Mi_Lua/rc4.lua create mode 100644 Mi_Lua/service/util/ServiceErrorUtil.lua create mode 100644 Mi_Lua/sha1.lua create mode 100644 Mi_Lua/slaxdom.lua create mode 100644 Mi_Lua/slaxml.lua create mode 100644 Mi_Lua/socket.lua create mode 100644 Mi_Lua/socket.so.2.0.2 create mode 100644 Mi_Lua/socket/ftp.lua create mode 100644 Mi_Lua/socket/http.lua create mode 100644 Mi_Lua/socket/smtp.lua create mode 100644 Mi_Lua/socket/tp.lua create mode 100644 Mi_Lua/socket/url.lua create mode 100644 Mi_Lua/ssl.lua create mode 100644 Mi_Lua/ssl.so create mode 100644 Mi_Lua/ssl/https.lua create mode 100644 Mi_Lua/sysapi/miqos.lua create mode 100644 Mi_Lua/traffic.lua create mode 100644 Mi_Lua/ubus.so create mode 100644 Mi_Lua/uci.so create mode 100644 Mi_Lua/xiaoqiang/XQCountryCode.lua create mode 100644 Mi_Lua/xiaoqiang/XQEquipment.lua create mode 100644 Mi_Lua/xiaoqiang/XQEvent.lua create mode 100644 Mi_Lua/xiaoqiang/XQLog.lua create mode 100644 Mi_Lua/xiaoqiang/XQPreference.lua create mode 100644 Mi_Lua/xiaoqiang/XQPushHelper.lua create mode 100644 Mi_Lua/xiaoqiang/XQVersion.lua create mode 100644 Mi_Lua/xiaoqiang/common/XQConfigs.lua create mode 100644 Mi_Lua/xiaoqiang/common/XQFunction.lua create mode 100644 Mi_Lua/xiaoqiang/module/XQAPModule.lua create mode 100644 Mi_Lua/xiaoqiang/module/XQDDNS.lua create mode 100644 Mi_Lua/xiaoqiang/module/XQDMZModule.lua create mode 100644 Mi_Lua/xiaoqiang/module/XQGuestWifi.lua create mode 100644 Mi_Lua/xiaoqiang/module/XQNetworkSpeedTest.lua create mode 100644 Mi_Lua/xiaoqiang/module/XQPortForward.lua create mode 100644 Mi_Lua/xiaoqiang/module/XQPredownload.lua create mode 100644 Mi_Lua/xiaoqiang/util/XQCacheUtil.lua create mode 100644 Mi_Lua/xiaoqiang/util/XQCameraUtil.lua create mode 100644 Mi_Lua/xiaoqiang/util/XQCryptoUtil.lua create mode 100644 Mi_Lua/xiaoqiang/util/XQDBUtil.lua create mode 100644 Mi_Lua/xiaoqiang/util/XQDeviceUtil.lua create mode 100644 Mi_Lua/xiaoqiang/util/XQDownloadUtil.lua create mode 100644 Mi_Lua/xiaoqiang/util/XQErrorUtil.lua create mode 100644 Mi_Lua/xiaoqiang/util/XQHttpUtil.lua create mode 100644 Mi_Lua/xiaoqiang/util/XQLanWanUtil.lua create mode 100644 Mi_Lua/xiaoqiang/util/XQMitvUtil.lua create mode 100644 Mi_Lua/xiaoqiang/util/XQNetUtil.lua create mode 100644 Mi_Lua/xiaoqiang/util/XQQoSUtil.lua create mode 100644 Mi_Lua/xiaoqiang/util/XQSDKUtil.lua create mode 100644 Mi_Lua/xiaoqiang/util/XQSecureUtil.lua create mode 100644 Mi_Lua/xiaoqiang/util/XQSysUtil.lua create mode 100644 Mi_Lua/xiaoqiang/util/XQUPnPUtil.lua create mode 100644 Mi_Lua/xiaoqiang/util/XQVPNUtil.lua create mode 100644 Mi_Lua/xiaoqiang/util/XQWifiUtil.lua create mode 100644 Mi_Lua/xiaoqiang/util/XQZigbeeUtil.lua create mode 100644 Mi_Lua/xqcrypto.so create mode 100644 Mi_Lua/xssFilter.lua create mode 100644 OpenWrt命令记录.txt diff --git a/Me_Lua/r13/MZLog.lua b/Me_Lua/r13/MZLog.lua new file mode 100644 index 0000000..c4a1177 --- /dev/null +++ b/Me_Lua/r13/MZLog.lua @@ -0,0 +1,14 @@ +module ("MZLog", package.seeall) + +function log(...) + local posix = require("posix") + local util = require("luci.util") + local priority = arg[1] + if priority and tonumber(priority) then + posix.openlog("luci", "nep", LOG_USER) + for i = 2, arg.n do + posix.syslog(priority, util.serialize_data(arg[i])) + end + posix.closelog() + end +end diff --git a/Me_Lua/r13/Posix.lua b/Me_Lua/r13/Posix.lua new file mode 100644 index 0000000..7536a2a --- /dev/null +++ b/Me_Lua/r13/Posix.lua @@ -0,0 +1,113 @@ +local base = _G +local string = require("string") +local M = require "posix" + +function M.timeradd (x,y) + local sec, usec = 0, 0 + if x.sec then sec = sec + x.sec end + if y.sec then sec = sec + y.sec end + if x.usec then usec = usec + x.usec end + if y.usec then usec = usec + y.usec end + if usec > 1000000 then + sec = sec + 1 + usec = usec - 1000000 + end + + return { sec = sec, usec = usec } +end + + +function M.timercmp (x, y) + local x = { sec = x.sec or 0, usec = x.usec or 0 } + local y = { sec = y.sec or 0, usec = y.usec or 0 } + if x.sec ~= y.sec then + return x.sec - y.sec + else + return x.usec - y.usec + end +end + + +function M.timersub (x,y) + local sec, usec = 0, 0 + if x.sec then sec = x.sec end + if y.sec then sec = sec - y.sec end + if x.usec then usec = x.usec end + if y.usec then usec = usec - y.usec end + if usec < 0 then + sec = sec - 1 + usec = usec + 1000000 + end + + return { sec = sec, usec = usec } +end + +function M.timesleep (x) + local sec, nsec = 0, 0 + y = M.gettimeofday(); + if( M.timercmp(x, y) > 0 ) then + sec = x.sec - y.sec + nsec = (x.usec - y.usec) * 1000 + if nsec < 0 then + sec = sec - 1 + nsec = nsec + 1000000000 + end + M.nanosleep(sec, nsec) + end +end + +function M.strsplit(str, delim, maxNb) + -- Eliminate bad cases... + if string.find(str, delim) == nil then + return { str } + end + if maxNb == nil or maxNb < 1 then + maxNb = 0 -- No limit + end + local result = {} + local pat = "(.-)" .. delim .. "()" + local nb = 0 + local lastPos + for part, pos in string.gfind(str, pat) do + nb = nb + 1 + result[nb] = part + lastPos = pos + if nb == maxNb then break end + end + -- Handle the last field + if nb ~= maxNb then + result[nb + 1] = string.sub(str, lastPos) + end + return result +end + +function M.var_dump(data, max_level, prefix) + if type(prefix) ~= "string" then + prefix = "" + end + if type(data) ~= "table" then + print(prefix .. tostring(data)) + else + print(data) + if max_level ~= 0 then + local prefix_next = prefix .. " " + print(prefix .. "{") + for k,v in pairs(data) do + io.stdout:write(prefix_next .. k .. " = ") + if type(v) ~= "table" or (type(max_level) == "number" and max_level <= 1) then + print(v) + else + if max_level == nil then + M.var_dump(v, nil, prefix_next) + else + M.var_dump(v, max_level - 1, prefix_next) + end + end + end + print(prefix .. "}") + end + end +end + + +return M diff --git a/Me_Lua/r13/cjson.so b/Me_Lua/r13/cjson.so new file mode 100644 index 0000000000000000000000000000000000000000..b3e433db2fde5dea2d0608dd9ed78da428656cfe GIT binary patch literal 37404 zcmeHw3wT`Bb?(;8$QB@yu`P@MLC)ACd2D1cHUeZL!dMSK64_R45uBoAB+WxoENceM z$k-`uCbmg@Q+*mGaT1b}Fp+I=Ze7VXc1n6lP-Li3es!4p z|NAjII+je5rnl++a_p^r)_$zL*4k^W{WyEh`Dk;?`l6yDVJ^2Q5rkS73Q>#rm0uTP zzE>n`g(c>RfVfs%&7#RS@W+I5+%tG|n8t<#TopT2D3(x>#4nNnk|>E#I}hz4;6JV+ z;lkfiWSQ_9V21e!Y~y`DNJkM}aGCrIGHeN~kL#3*rvFa9q3Am2CIz4c1xpdXut12* z5l=)^+bAZ;N6V#(-g{9Fw_+pW%Mg1JfI>{Aiv+5QKx@Y+qDjfH|mT#5MQV7DaQ>6ixFxN*dI#~-tM{$ z1=b@3b-@P2l?ba5EQDnU??bo?;R=Ln5vV_P2v7}iE5f@F?nIzI*{_uVuGa~*h?gT! zmsTJso4^e&>Xo^u>zfUr<28uigK(3EZ%2HKzF&*@y$B!B@OH$?9);MV)0%Z`qOIm; zj(8i=cg&IZ^Cr{Yc)6j4S3+>Dup?exXbE+VcFJp!_~-m}$TzST@J0ye9Ed7Ik%iEV zwTOw2IiW1NlK4{9e?zZ19_ay*Bs}z`_RK z?S_x)LK-g8`y;?pHaLs&FF=8wlKGd4oyecG<-dgXCv5OND1X8R--Yt#qS|~4A!p0~ zGU|`m;2`SX3j=INI_3K~;EXN*Eu<%Ha0qaR4W=J6mxY^SXus8#A8|o`Hu%RV6STqK zM!sc(U8wK1!P}89Y;XfWb6L1q3VP0VsuapIf$~!}I70c^;J-uugbn_75#(oszmNP5 zIIMF>r#wE3IA_cM81hGK@V@|m#s(Loe9{I#Pkc7`PUN@R;CaXo+2AL@L(m5QDPYS6 zKM2@sgO{TI4h&oyixj$XyD6Uz3CjFMz&Bxo-+}xS zHuze=IUC%rs>;7h@b?DfSHs@##XaS>0qu=+$ZU!KEbwP+@OP1w`;ZXNB4+uIBTm}# zYf*m)gJVwTA3^;NTmJt9+-ifb0UWZy2T?z0gYQDVWrH6Tkf#lP(v1Wg{1Je!gP#e+ zq^}G3&V~UP(i+txL-G>1jFI~k7lrjjBY z@9pmu;Z1Fu!_nA4th+arjt#VJZtP3+$J#o(`eG_?_MC8JPbU#{_Vs=wCi(_D!|B9e zDmD=9Om~V@dZ24C9*z&hR91H^olYcKswLbx&>b5XNDRn2$-z|5?#{kJRjjuk2+}Ge z(%ILiD$Fe1-`gkR$w;DqcNo;G!jXYkXF8UaB$JZ<*gXYO2(y()q%V;g98e9VV(EBq ztS>5?0@;;WLlSvkZ+B1nf+qWbthc{gR)7Si zTTJgwDmqA*tq{T9{&WlivbCB>rh60p5(^7-B@%tHP6@Y!C0VvA=me7=4)>*_@zt_k zI?+G4s|#4e;oekle_Y1>K%ynt9ggoxn_e4;r3zgNLi;;+K@lKRA`6FOcdNKt!|}dE zXFAmzCC+ZyU>It#CmK)n>ZXktv}ik%z!?o|wozTHLVs+J>dk@9dmt@U+a`8Y=#T75 zlFI%_XDY^fO+J#+iKsw-MUs1EtH7bVF05`9I?7K=Lh7PK@1PILLlg;68IF87r6fNM zg;SmJ7>Izy42a$FBzia<2O)#$h=}chLhgd%CL%_dpaOad1>F_vFG!enfu1rlfcDy} zXaTWDcBlGv17MLBHJuk8h@}VivYANFfQa8S(3_5l&aMRQr?;y+5(%ejQi+;Xgd)5P z!rs%AaX2n*6mCF-W9l~Cl}dq{PE-!ZagU7tsMyfh7`_qpc)|4`T zUX2hkc{5`Jsq(Ut?azg&DusWi^AwJ0m=tpj>D8E8tM+0l?-C)*t4FwG98!e zxI)L4j{Q0g=(t+PK^@oXxK79QIu7Z$Nyi&?+@j-F9k=OthmPBI+@a&Bj(c?6r{koK z(>mUx<9l_S(eVKtAJp-Xj_=p;w{)DjTgjtzK*e6nlcjugT(08^9a}o~>o}m}Y8?l4 zT&v?c9oOqPq~j(XbDhEV+H|}_$L%`q&~a49Jv#2waZ<-=9q-Zcy*ke5_<)WN>Uc=U zBRW2$k0DV5*795zW=~7-_#e}Q~7T!^NPpDE5zgD z<%qE}A+?cZo|9!BbO$nD$ev!bW?7HUL;0+|+;LmEWO&W8&5INcmIK~wiI{R*;v36| zGo#zB$Q;O)XZB^M(g*x*(coX|Z4&cELv^tTmEDi_+`?K!nh&Fs(mGoQ|uBqRQ%BaN>8BMU`E zW+a=>Z>#d%-yxcIyN9p$4uGGKw@5_%fX6cBZgJ_ek>6B!g!tFI@Ddm0-nwwjvXglw zPwamAL%WxZg0?JZ%YwEnXoHMTNErvUj4L!xtbYjgPXQlg_!{^VVtI=q%R zTI&MdQkgf6w-WLvUbi@q@yfh?g?jTKOG_L;n@m$}zLF1lzY4fS9Lbi7!&$Kus(7_q z_{#T#fg@ScAbcLcQK=6;ERY_Qw5a;uL4DFoTE2>OCq3WGU&!Yl$~L?F&ubY`zh3;C zY1`O={zYG){~sS`{}hR0Kl_ILPdT~z&JWq}Rcn0Z&?ke_m&ssc za7(-;qS5QKge#Ewcs7InV|&EyjXopu+149KXBnpd!wZTY!tFL%44q8hh;+h5VxGk1 z!zKcGxC%VD(C&1b_d%B)&%O@)pfBr>9z~udZTBqF(6r=}I9Z>uz0J4s#gc#4bwO8+ z42Y9y)L)ZEIj3Kmw!I{Dvo+n|zZW)FE*@Ey7AMCkci+K`7=r8%im#0y5NF0S;`I0) z*kqFSSl;1k6-|Du`i4AW!%&G>KU6B3VTTP!>pAWb-N#Er{CKH|9sk#SQ<)d`MmzP0 zhM|CzRrEMz=n-AVB|kyzMM~LPQno&9bSgeA*AW~4p<*L*%O%UBoDX%0%a;ii>I5?P zWnVrr+l?@?P@CY`c#36K9Zx7%+Od&YvOdnw?$V%JzL z!aI=fM|z;xJywqpL|R3$7=uF{6UCk?Nt@xPocoXS{KDh%`vPv42XgsIz6)Eh4;`l- zzER{Jqh4Gt7Wyv(-b0|L4{4W(E8wfCFN^(`iZXv1Y4;Z`96N&GM|c#W4q+1EpjbF| z9N{#=1j17Y&mw#aVG`jvgclHAM3_Q&1wnW`RVTz1W3P$DW0?JnWrS}N^i+#%UpP(%B zN`x0%LkowQ|9U>et!43wt*+@`)@egs=FjLWq#IpfS>`3RFJK+&*m>aF16yTYv7u#_ zdYM`39n|$oE>iCh>cIxHu)!>BFbf;Z!cMcW!z}DH3tKGM_J|&X?*HG)wkKfM0gRJD z*mEHBDLJO)q7D9I(N$ITuxrR=ybd;B3!AQnO}8&zv#cCtD==PIA~*iyw-$KlAEkWZ zZ;VaVUG20f<|&&pa@rVEKC?oc0u7&$vTeE|RTW|%1ud5k_$V{euY_g4209%5YUzFz zE`!@6wpE?azfbn-kMb6_lU#oAlLaqX@IrpG;75+jAuW>w-&Ha>2$^sk;Cw3zp3{&+ z5;Ew640^!3?0@hKKNUck?0k&Hr0t7vAJ9G7&xDyLa1j?{Sx={9P8Fcfej~5yavU>d zIA*Z!QTTt(XC|dSDp}ALn73NDSy8-XnDtzuvHEt5*}Sj794CX3VG97}5AuT!K9Ys}vXEaEGR6E+ zjWv59dpkZg4!R|eAJSuOMdlOPHqgR6pZ4Pu=~v-z8>=acC-WU%$YJbRl<@^ZGvw@~ z*YL@C1=tj6)&Wm;8Tdv*g1VajsH;AoZ>ViS-&h#)I7jkX9j*}QI0W65^2At88kUOC zJmRs6SC5^}exrr~>A$5_o zeNxw-EuZ*DbbgBsx6!#c@+fE8IOibbm%R1Fj>@+0&~0<>Q1|@LW=k%IErno9>?uk2f^s}G`wp8vz+1YHV{vx*Y4z0U^pHg?}OE^!W?%Mm<$mwV4<03I%%C1y| zt|sjP^ubHI59aFQZ)!Qu)`zVB?Lz(8`^nU2KaD*1->;wQe+l}j{y*4HtpBt0(`$e6 zAL}PPlmCU<(``S6Jq3Tk{X{>^dGuVqn6?CeUG)yEkLfQwq!Bh1gsi`(ZDKZ?Vg7dt zHZ!|lJ)>=IF5mYJoll#p|8vz>FX(v$=TO|A@M9jqy$b2mFn^%Wuttyj@c|%hp~lp{EZ%)Ii8a?GX~7n?reUN_&5%;KF2cpW`oDH z_XP6U=4^dxug^a1`Lq8%eOi5yK0WdOpifU=Zf0a${a?_hZ1bn-(?frHVV`o}N7?Bq z*zDZ4+VKmv)w>+FT3ft&nEFb+r7l|9UTbah{gprEe4q7Az4>CBw24aEda=K1=Kh$! zO4-<{EO?)>Cv-9Uu6!H&<~*G=dCY-5Xp+j`RtnVJ`i6P}il+r~gFh2iM52!G~Z>!CLqX*21U9gIEt%V?7vvj`*Q7 zzWZ}HC-?ZcFEAy1BNM`xn^HU+%ZTdY_%O#K?mb;ECa_1{Axgog&kAWiJLDQ-v+m>i z88Wcs+;_e9%_X9F80*eq^0n04j5Bx*fe-3DA)1DR#a+YaK&x7-+{b+{CFeTJg?&U> zk88pb(J-dg!)LG_K8^M8BUlfg!oKy%Echv`i|OAtiznnbN}7#4eE~%?_Q~ZqG8@g$ zX+Q3yxfnEK%#(U}5VU%P-Yc*|l2?`wU=1t4XEGWY@_|pcXgr1!kB@?G$pd8I|1`uHI5`T~|~MA9!@=sTq!dW}h1Fh+aG zgIxzMamce|7MhGa&&8rrp66l_>=ia0!`|W$^ybm|kgvxkU&z&ekCw09M?hz#JRj0L z+UdB|LB}Pt(s4lc>+7+POFCYU@%hqvw7nm6tVjPI7@4M}DQEf{Jo>k5dQ6{C4(t=q z5SVAvC%3I#mZctT5pL{-G>cB`$wH5EMWQJOzf{1_XI!^x{IvZ&f2eH#QP@893I1!m zUi=>H{xt0V5!n5y@g~@NNPH7M;cM^-XW$1;j$8Bvxg5^mlaDPVii4eJ7wNC>&Ap`7Hd)r~3A>InEWx3}-e`xl>{F>L=;(g!^XGV*ZEh)X9 zS(bI3dQV@>^ttr@K>Srdc*}yfEO^GA+1KDB&cH{UhL3mzKH?O7#K|mpm40F)=6=pT zxtNW!eU>*mc`+O3yqz}AIUMk&Z_=obPE*#nf2AnGwT|w#;d^^&br5^nQ zo~JQS*-$fRX@U)m0&W_k4cPI%p3jsKrz%Uj8$oyDn9sWpbi4h(F6Gjg3qa?J#G6$s zymyK>AbaYO(ZhGTlpaoxtsSnixb~01DM>r&IXieZ=wQSGz2Pu_&OQ@wnCwI0u@HmEfbd^Yj_n)b~(8+{yKByVWCp~LTL30?;YZ4?K}KBpJ`9% zvrEy17oXVV|A_NPzgaf}q#Y%=w0-)dxP-Oz3rEYgP=<-3QN!Q;~_^Bj}lZ{KdryN*8itQzY$<~heY z_L~})URrj&^^|*|9P1|BzH&FlH8a*lgBgsyKFg1^`y69k6XrzgVe9w7E}o+Pj{Qi= zW53p`3&%E&g|q3o({~tot%banNSvd&x%BFbTCZlqi*ZQ$Q>Xo?`N3$8;|z7BefbBJ zZDWpd*ZBpWw=h33_$VjNefb>GKKJEZMD`u#=`2fIqh50#&)_Hh@P%3)-1}?r|D(3E zA@o1X69=DJv7PyEg`_`TV6!z};IJAa(T=jU#^EP_qI|QVk$6ZWbr1W+v-12Kpd~8n zjpk+>UtZTZDd*YRjA%K})eq0xXtvuwX}s-i>bmF1XWfs?k*8$QJbZADeAcx;fARjW z>2(I@`ue;O)*2_Vt~jCB7^l>p@xcrPjD1qt5Nxg5K98|@PC}1KaxAjs^LG1ww>hJP z^C(g$-UU23Pq9oCFRUuTIg|ip3_Ly@=z#wcJ65h)_89cej3t;?4)ZJn&hf~-DV8NJ z(p0Sb*V%_^9dQclh?Agu`Wyn+3_&@63F&n@2mQygFKM|=&mk?D&u5Ix=bkO~ifgpz zzOH0<0&BDYWaSYfL$z3&1+g}(hWt=|yaj8tjaZ{KVeJ;eI;|e-v^uQe@GG@Xmx|8} zWiZ=RbLNI2-$)LaM~`0yz2~{6%S3b8WmqrLAA3Y_jCI(yy$<0w^G%?s0r`F@*QJ=J z!%vQtqV9)9lb7d!iV0+N{(2EH|S(&+ecX_FWI1y9)ZY5^)Z5AIwdKDFMQ z0(~#&_T8GkOJw`g`7b*1uSR{g_bt>dmhECa1%BwG%EeKhkIj~1twR6o5le>4c{UN( zgPI=hRbZtyhPBwfJ)p}HO|`G*_d|XSQhtAjIWx;Eo*G~W`(*w5;|>1Lqb~6p-Z6fa zk!SMGI;+7WWAcBSzIXD!;BEN#y*>X@-WTJaG*SlO6a1_7!;tpBnpLAK``ddwRG42gtpmVH3r@M4Jllf+x zeLXDC(azUfbEPBrk+?dm%l%(*t}%E-^ps_A5?gp0gGtTbJmHpY^of+j zJ72>+Vo<^*8lKF@(biL-;Q(mJXd2?6;Yp+`Uhk5;;+#yOP7HZRWSx1aW63kkQKTIJ z%?GNt!n1xgAA?OQ8Yc71`-J3k9{6;YZ^<9SK&J zcM0fNJ>^%5IX}kR`W5AkA_YG8`*0%jMwiIs-TvFb&+X8UDa@lQfxFV$FDg;)YH|25 z{MF&B#F4}Fed?^{EyI2}j=wToBECQD3t+Bm?E~G1<(Tgm$Hq&)n^fke^+n^%fni#-EJKWw&LIXdOEXTaM~E9&H%*G1dWCjcd9d_?ua;ag09T{kq=8 z@E=3D7G2Kx0L*WP;3u$862v|dbWVgY=TvQ(Jb`f%=Yhv=lXV-%7DATuAY1sb!#ub7 zd(yAa-_Fh3Q)qjoZkxV{e9;$)lEy$l_ss=lLsxKNOXi`?g?P_ ztNO#+h;@@1S4xpinS%c@p=sWQ@`{EEOq0}{Mu;bd?#H|aYcHNVqC ze-`+&z@G(v%u`Nbo^lfN6g3~|!F&Y20~o@yrlI=c2>f@0e+6`?1il1m$DL~lK11PH zNW0`bu|#;}Jd!Z;JYqifgzv-H%Q@w|;ny+VbB%K3`v3$MG!(3IZLxKti zo)yZoq(00K&aX{X`JU}?rMcF|c{~f_BIn3#YmuHC1Tu%R+~=vtd=ByF5x$(AY&=x8 zvHMJwH~oASwAOpD`^_q!7cUpUexDyWNEdlk&)7I$&dIq*y89XI5vx5h&XWV;DCTev zWz~8NcxkY?%(@%{+&3$@4l_O$r0-)h>c)$MVwoAUHM zn{h?)n-$1TKO3`L?VuM7N?CG0&D5cZmb)0ufABUm%e5|P(lQ+B zUSDW?o^G4GQx3E#jDymDU#!hT`NlH&d__E*{S0i2eEqa~YR-rLSLo>;Tq+mPp_8*#elKwGEH8++fV{n4Y{&-foP^ucEl2hN}Q$B^cg zzl(S{yIAuUfG&pgeF^yBvnZD5I=1w8l+Vw=-*Zh5+Ti0)jo%AjzX!fP4PT#xukVAe z?}4w6!q>NBJlKKpK>C&u_4_39gz*Bh56!yvXFl6h&lE#aC(K#%MMY{)p$zS_KF>?E z;+YrmH=&(Y`TMTN@VlY?)pa-ry%A^sFM*%0$34cf4Ds&7Sj!ml$}sNMbM6Y@Bure4 z>oIIE_4j)L^LZB60mR8Yk2-O38J=Y>Bj0@fKH0ctS%tKNY5&M_ z#C?A4nq^b}u6!rwW0z|GeGTlC`P8`(=rFw6{rCU4h`w=LG5R)hdnLc#3 z16|2NSMYm}-^)T*aMu1z7P^v!u3$Vrh4K6(#`Ed@mkh>kw#T-}H|d>#UK2n2`qNrJ z$HZZ;@Z3_xJ%@HNiZ$b?Tr+<_u4!lAb0Ch-q3?b00c?YN681d@=1(~C?RyT)_dp-j zo`ahA)uOzbo6g*GSZ4Md4yru|A7souhh>sJOSbRab0B`T=P;db-*aGoIqI{$Z-I91 zIgpnY>@`qM=u_+?9l@OFC~Xzv{OEXH(S6=(=-^JstzH}*FA_@%dkz0++iUodZLfiN z?0XIEs7qY*73z5_-Y2BbdtD29?E4P4fKTo@G$N+n`85B_HUDbQVVXbto(pM)e9%AB z`A+^VE$_McKMMZ&n@`361LBC}|9Z*)5%4urx6E$@Ue*56E+6||#LGAXLw+cGyL>oj zF4OYinw@JH<`?0fGP_*1dxG#jp2fxQpRrG&Y<%V(#A5tzPIy~j^C6-3iRRjWpr0Zw z1`oXTKEi`~|DhK$B7EWg!}ncSUuBqAD&8;44C}IH|AD^8>_138;ZI+LY?Mz@`%jaL zKIHd3JK_GfvkN>f;i;mmkKq}!56^_TeitveYCA<&Sw@Vm5T4Q1UxALWO~^;m`6Z$D zMPNq~{(^Xa8S(u9_%QdD_znS|INyZzuDkYAVn5cqhgZurFMiisd8`qM5?PF#oT_UPC3yK|c;}#@c8L)>vx2 z>-C4=m(+Uq2-dr!SnpcW_i)Wim^}Ktt#XV2Jm$+>=lY87Thr&C)^fr)r2KtgbcVk8 zVSm(@gD6McD3sIdVxw1n^a0nczp2ZGfRFuS)~4(Wvu+qRVma;~iCVAC9kl*8zWDSb=#D z_wHYUo>Jm~(J$&ZH|9MBFMLJS`;Y#9@oBW-%VaPa<9t`h@#00$Qg^jEw*qHT$ou8} z{UYT=c~0b%T=?4q%xT8&#k&oBbQJnWurH|e(7k0=Lx>NoUPid;Em%;#@74I#z>T@?l$-jH zcYFMQ4;{_Bi?L2RkTr8PZ`+z>zmIk9YrO{~eMcUgmJMkjjg%2(LHgKsh31iM`>c=% zq8!&oimp-0D2wvYM?Ob50UV9TNe9nIJKO$kTiXHMHqW5od1$p;`)bxL755!Cb*1b; zH)*3Cd=fa$Y5HIJ@-+SBVjtHq1^PK(ArI_h{K30AGIHDr+4_m;^sk5@U$tK0b2}gO zo;;Bs!wX^7RcV&w?_N9?DHET-8jStXB-cmS#~z+QIj=Z8UIzUs|8T)>OlPkTIT|IKe5`s?T6=est3+Pjh!eaM+M`5mmSHh%cP z1-ANUNH?~6?zg9Py#%_BIpR=&YH*f)ax4et{u%kxF*85Q}({TTe$K4gghgENqUN{nUGwTTy-(M)BCVQ}-$3GU#vOcGsVCdJw zV9(e&o1=C2>ORSB)_Qo zf#a<6w-1~@k&k+<_w+v-(M}6#&E@zU81N~)m*xt{nYO!Nn0zfgxWpx-U!@*m&l-IB zf*Car#JRN<2j3@YTp+B)FK$ZnzwIOLN=AT9H|3iO;U67da^#j$_2hzAX|5upjtXb{%c>U`TMDO}SE6 zRo{oC-uQC0u2!kbnNgEhqtE)dq)#Bm?YXB=#^l#w?aTcY;$Lcg3cqn_a$PHO@Wl(q zI8I)IX%goV&q$d!_*vJ`#h7K74|!of;tcj9PGdjf5j@dYX|e!0FvU9&_k*MdIv!p5!>`@Ch+Cr}P*pUwi_$@Kbt#q0~n zhcdv~!%^(h!43{U|Mq9VuK2fnyo~D{_Br$a4ZfTCpdEd23Vm@B&wA8cU(!hqe3^3!RVpPKO^|hi~2N~Zp=CL z;YIikqYo>g4`#e{>I2*0MSeOytN7vkx&k`DcZcyez4QkhmqgNAmCrALtgjj_7ONI< zETQe4&wH!>9r9zk`R%M3R}^-18ufb5l$+(-MY-P&-{pXR%>nOlz;`;}_dDP_9B`Wh z-tK_6Ip9_Yyu|@;cEIm(z?&TKMhCpX0k3z!%?=n(b7#n_!2z#xz##{Gy90i=1HR1x z*E`@_9q?KQyv70F;(%{JEFO)B=gJXM=u}C@=wN_bpaq^&i zi|ppuuEfAzOTIL=lkWz$ek+61k_V2X@ zlk%O*DWvre_VqDOz3Enn@RoI(n*pz`Srv`#j>)|6`fD3(N+;7jtf$J)Ow)DM3wguIfmj4DC&v4Bb^6X&EE&d2 zZ{v7xu}SOg2j#udu&M(FG~Nr+`4-=JVz57I6}UF?Bio|kjty(AJNo&`Xq2$xv2>(I zOAqgsj`wyC4oK0mY@rYIiu!v zB8XjsskFtMu9&r@bBmQ2ur>ib-dw#`tSsCKVY?OS0lW_zSG=z*29rs=P&fwuw16S2ow0tD8N}s3+FGUCTBX}srQ2Gi+ghdDTBX}srQ2G?wphPT$7^)l zsbjXwe0UxiLq;;z?XP0{OAu~AXjgf+AgwLDwc$PChUN{Mw&?rjElrS9TX@~JZH08+ zoAm8%+cs_4fFdZf<&Mn_&D+#{!`7`W&Fi-4^p+OpH*Rf#8)LkA^E$)^$DJEDwKZ>V zUDw#G>NaoNwsjlw@7VI*EnDv-4s$)W=B<3i+PCs$hu{!7Iz*%8@Aj(I(~Hv?;-#oA-tb0mXzX3{>{*S#Ig~ zh10=AtF69Re|Ne^-Sfrmyu%4{`ZpUsRMsU?{q8|&N~C2bUQ^$??cEZ~1%8t6-N!5X zwc9XsD-y_0JTb7VGfgv9mPD)U$E)H+B|J+rU;Dq-x~CU*Ua;1bHcc3XdewPgISjVD zhQt-FsoM*;zq~gf!~X80AUE>5W)ipM-Yx9oJM#I;VFb~Av}lhBEl;O=6c45X_asyn6z3^J9pg+9bpY#4Xq09`XJ72;&QgLl(X>h4?iep5$PDUcU_OA?~R} zdx#G$MSFp1?`K{)tQ$rU zG6+e84un>O5JC{aLhvF8gtJ&{Od;^wb(4rE5KbWQ${`*>;CJRSh?58%2)tSmhY*4Y z7J?T+FyK1d37iO%2one=5cqAj9O4m#3__A=2(1Vqgdl>2;6>nf(FEeN9l(Pyi7

RSSeupfHc&H6H5w{|Q5P}F6f)_y`@Oxlq@r^Hj_iGY?^4rkZxYi16 z*mB2m>&BWJYgX%ot@wh`odf9`TJVvsfrN!pR&7n)%GJSDwKc(7Yp{Q3f8w707yhOzm|rw!zS6&s z&d49J;jfc#dwA!J{E#i5dXKN5&dA?m%cnhDEI(vjJfHS+vHG+loPn6pegG*(zO=WC z<&v^IMy^X3%dT!PMusM4j>t+48-x9jE@-+GcJyXc1e{=Saoj!g`)mh(;pWjNon10Z|IrHuPCw-bj z|45RNAAK0E8S=5yzwVvL?d~5GMe}jvaf_PN-d)&~M2ru($(UcmlXv{+V1GOzYNC7b z0lMCZsOjlU^+*tvRGc0VH9%K`9SON_YzM-~8;JF>x~P#~^Q(~`KdafisdYQr8|+Gn z8u|Uhnt_D;2wF|72Olit$L?w(>BIoCqdLNdqr^2pa`<_}n(jmzM5}v#F0F=Jg8aZ+ zO){#9!Y;@_FLoYd$lVod#R$Aut^|SR&oqe-<=#Lz2!Q##7&CA%xD0{i_&sfgNmMj-S;mWSD}oz=<@o(= zhK+pNt z3_Yl1@)?s~>MrY1HvH}|!yeq5%ard&f+@%E4>M#on^KBbLTOt$j=cOz7 zAID4v@@6hm?&C;cIr8g`s!&4H&=+OQYZ$?lE8mGeM=&^;Z|?8ccIt2aoHXKo*{mE(8YS|O+rq?yaBZ@!LLx9jEnZwlpF znV>J0BhGIkm~x!o1b0uj#iFL&r*Y4ICLbY`t3^4}7Lp40e~f$L3o;QG%QdT8`ImVN zf2m`ch(GtDT!(24u(^E!_dkZ7nsP6GWV$V;5f`u55X^lm=zAUI?3c-dT1p;F9?Si! z0T7#VjHxT;>dSy9*jxn?HX@Kd@=x24I)Iqh1D_NkW78{xPvy@1OFzTQty`r(QLt*6 zK10E(1^NaBs~h?N1=DJktt*&TsBBuniXhsqg4vUWIVjpE7s?hH<7;h<3g5SNOAn=N zi|KBhUgt$S;^o#~b*HT;7*2;5?E;rusD`Qk3Rap!Jy$S$1?@vmdAS9g0xq>~A*?35 z)K6Y+Q6^*Rp@QXPR^BUkap7LUS2|#NaC1SzAV2EJAsfu$e#!>l4Vd(k1Dx*@8N^M1*|UV{hfXWDB|axTUqrsgM$f~5 zjr@zmJH#2lNjh&_q(@0g!Q}5x)UEtW_^)usH3bgz$nq}(<{F|EG3&pHm}>?c2$k?V z5pxb(uiSj+v?$lUEl^#;O@KM)Kc>rn2yw`ohWR~f&gqXMX8A85<{aFD zDG)vbm~-~$bpCe`bB=G|9|Pu^QI18>uK-|vSCX1Y_$I)d z)1TM%Hv;Ayz6TZ9{s#a{eMJZ1Y=MXY<{bSw&F>&!&dJ|^DY87rdN1(TOZ*GOmjIiK z@_vZnN`}6|CTELTUkUD|Z|2f|XSac9l z_}TB?4hhQk-VfNa!QFs^HaHD9WP|So+-igG1KeSQe+#f_-;4I~BkCF75C3hzNn81c z0NcMmehP5Lmj89YBR2St0CSDP{^9q)p95^JCAfJRaIQn8P@X>qe8L8MpeGYHxD@cD z4PFFz$_872&)VP>fVro^i}=?9wtxS917Plb@M8WQfVm&Si*P4kuHnkiMWjCsn0qF? znEz40AsdVX;xoQi|9QY2wtW1cX~y^NPXo@_@_!#N=X@L=7h*h_06b#L|2E*94gM>@ zTmzjeQuO@*@CjS~j{#5E;PZedZLkM+Hf4ja0DRU4R{=Jcg_{syuGyZ#J>{{D`CSOO zD31tWxhJLJ1YqOOy(qsMF!!x^vHWiW=3W*r!Z@rb_qPm;L#lGm%fP<_m}}BWS^iS- z2Y}6G;pTgQYxQ_Uef~SZ9X9-D0Vi#65r%lKG0&kc`MC;k#+Hwl8p?gR_9B&kGvEvT1u;I1H%C2H`8|Sjgkt&OkW5D;&X@8mxzB zNeYLfiEwvcqN}qnESKHk&cQuAXp`)V;WR-8Q!DtrcN|*7E<|E)SRDjYY5GVV z)68~*EUVAM>3Z_0le~|_yYUTm!ZXjtIWwmZ$rW-gI4W1jnd`uuvw;2NoHI`!z$%=e zE9B2{lCB`BFptztL#fVqOeS?D5`E!BlIIXpH8hb=u0-KljI%> z4}+{$C%;sA94TN-3Qi(8*u0;D{lIPxj9Oz2R+jL?O z_mXZ(BRQDr>Fe$8Nf%O~JUDHGy`)%7gyXVcxH}n+_NI~|)vpe@NK8r4)(0YRJ|@08 z6v765_{N%`%<7K8&|-a2S=7e$hL zS-B+~>4`;lrqZ2}ou(!`G#rlX=?ufDacc1+l2=`_4{Y!}py5&X9qHj&K1dU?mANTH z>+YG{Q7{^b>W^@DIKE2>ky2BgS-{`hM{Ef^v{05AriLlf`KZq#2taFN#S^)=lg&DljFMI&|B#X zdJBZ?ilwnLh|{DZys2$-*tj_87kyrv%mFGYhSq$FA{>t0eF3(C&U=8c7rG;NEG2uI zHS}0P&B9@*LT5VF8;wuvjy@V@dYC#0I|>5_`%;oN?UH8@7Y<`169qdVE~pJC0Cjyc zdA)!E@R`Q(HHz{=^}^vyty0oRGz}=*f^)*%s!yqTy`UCEL8$@liN;gC)BMQN616^i zi{2VcM+|>jlp1yXo`K%9@dVTkbh|l07mg`I+|aVMVO>jj>-zQEo7=)|>l#{`F=p`u zW^7Nd(oM)8rqoNWrnOP>sCou;_FrJJ+G>>RBYBaY0a+)VFq%XuQQ@S!3dgx`yxXXi VbQUZCdOOq5C2A63Dzjnz{{lk&Gp_&u literal 0 HcmV?d00001 diff --git a/Me_Lua/r13/lfs.so b/Me_Lua/r13/lfs.so new file mode 100644 index 0000000000000000000000000000000000000000..0a5c165e535fea259afae448a6e25fc154a1b972 GIT binary patch literal 20493 zcmd5^4{+PneSfwT64F4F#1w~`@>8P30)miZ2NFn!5+@-k2DD&Sy5=Uyk{oN1B_YY+ z>{u&kV7WGnOiMS@W<{gn&-ExaG`-Rr4Bw!=tYtUv%I=zR&yfec$(e|9`*td!O6cv#Y78Nf_f1O9WAExe#5T=T79 z;fM=FP+Tr9W7NVs_%pT@Zd$Ll4Q}v&+1IJ;B8i$Lf92PNuqLs)S%?(u1v(r~u?$R% zod%{|49h%z|L5ft$XUjOpA}$vFg?ukiZuQUCzM=%kC5_pXnGUqGDHhN9~Xhvu2nLe zfqb$&7b>|UV7wIJUqt+mfcjwJ6qT+fu>!OgiC2P7BHb#`C9rT7V9BSzGNb$j5b`OI z7t$4aK#{IMts`CGV$kn*n9|Y0x7S@`j(`8{CwM zWLV;Wzw{0Np1$9WHZ=#vPzn!iF_wdoku}ID!VuL)l-h(P7)?iF3W> z&-y+evm1$S{tyY-S7}xcu#BXLkg>Zv?z%QZkjG*8V_^(8Z zW8IS<+uus?+XH`&O*B4)_W>W@WcdGPhHaC5RK;H=KH~~!O!i5d709oQ@Chq@&K3V# zkf#EEG;C)-M$&?Xn}OpNz8mqQ7JdvkV&S)d!xp|C*s<_ufqfSK8AY}5K7>!R@xf4@ zD)5wrcfljOj-CR2%og@ z&k<+B!hZ@pZsAveYZmSUHf9YRcOw50D?ADQ6$@X3@PdVZO?kDUd;#FN70wAUYT;er z7qRf$NE5cOh~33fpqdfw&4@YrSBaorHiRVIUxoU*=#;3 z5SlC;lq69WrGtanOzwc_FQ(HCNitbvoh}x0`52NU%9(tQ3E-12%2@l;<^D`Mo06cE zE)S;5$U>?Bg&a=xmog?^IbWc&CzdaibrFZknZZVG{g%AcLtj3hO($|PeWs)X2h&te zNqUy^rid)dpl06?u6(edxF?J0L>ZD%E^4`$xEHbtq)wEt>ylo~FqzEeOGCx9$QRPk zv@D_4<&e~z6hBeiud)Es6k>q^)1{dpIiQop%K5s!hjL1@nQS_j7yZdxIg2#KK}H=s zz<+2ld9T!=&JHS~s*SobxpEqr>O$Lkq1aX^1&?L>rFwc;>5ckexl!nPZS}-V`>dD$ zo>==ZIKZ zIb(gLQVfbOG8AqE zqf*WtHruyJGz;zrxW=Q2u0Mz+tw$Wxe(`uamVM3QephB$tb6EH&~#YS`!ua+dPvj5 znjX<~MAJt!9o6)hrZr8EYx=aN&uBWX=?P6=(DX%3Cp3Lk)03LMrs<@nuWNcr(>FBb zJk9cXHC>|VGEIG&wrJX_X`7~wrU6ZZnr_fEtZA2~n>F34X++Z7jk4vBp<`$$SfWYN*-&2$HnJ^i^Shu zx=8qgJ4H9#i$u-$!Z~bxGPPHKIOZ|cT0{_bI{TFm|w(O{D&iA z+5HbxA*auQ{LAitw0hxb%DCf*EOX`a)j;J-)l_FPuy&-o`ST+yMO$U0Iy19(y?-<= zPHgfVz1mkqxgx$MkqiJoTWR%(r6-f0ReXeacSiWgi@L8Z-_rivjM6FkZeV=Vn#Unq z6|z+!TNScF&*M_hVd$CloBf8;G1I?^^v{Ez(Op!B&93|Lv8NH&U#W<@#PK6W4pWDI zM>Km;&Q|2(4@ZQ5b6nO7>y`PPd3>(+G(J|=JmcV*5>I?+zQ@nOcQg2IJrBM;^YAq~ zh@OWIPXG1XIxswUp9jz5=inJR51!F^cn;{g3WDc`^U&$(+T7)TL-P%shpflX!P7Yp zp3!sgY&#F0XC9lo>_(?P@N78`ogP02Upy^1;yn+(J(_P@<=hHfir5t{iVFhMoln;2re6f~ zUidYM{uE{DwS`Yl_rr#1!ruD*)Zu)sanRXHU=%!yYDAQg5Kf0UY z$8?K1pL*Ht~S%%bAy64TqmKGE&N1De1J z(we7hCFz8xYZa-_6KElIJb_C{Yn2;=t?#sU zUj(;T$H6m4-!kpSGdf?3c4N}@IoeH`x5919Pe024$X_Y>Phy^0BFEz=+q*DNVVoWd zW3JkOxhjacDuB7FNh}I*9OszMaomqFd>lIQ8voIV^luf%s{*_(7Kc>)5#@h5bji7H z1H$}HL`Yu2c(dZ=G_%YnwT>CK12W67Aj1w-N3F2l@S(uWj7NrFq4gKB!u^gT`LdjE z&GHv@|AOVuc+5*+&UGLku{~$`gArl$C*?-jmm^6t+D5SQP_Y*ZVVz(59OM&~p_!u0qF{d!NJHtLEIzdJa7OHB~oUL$GeBfAB+{a2=uQ z#E*U^<+19-XUR`}W0fZLv=;5nk7tJ|$ij4fJZ$!(98BM=cvtquc(oyIt9YV4V9TlN>oT?x$Yj?U>w^49KUAH858Ko! z$oD$hl9UZH;<>P_x7V!lP`9iTwGL|3MScDARaF;3T^H0JWEy)Nb#W5?@Tm06JzWz%)3=Q-%fp^Ej9a3#--j_KP=nD3zXY$l_Og(htnL@<(Rh0 zQLjg&d8G14bq)B^?{zDVmc8nK8nPV5^QqAl!aw@uX0(i>)bYil;v+p`rwP_%zjrFl~{u_FL!_RiY>EbVtVqu%gXmX^ga5&jrHpilmqowMO{@< zPgT@o74=v}JyubVa{YVCtba#!{exExW%ORQrTw6_2JW%K+wNE4cU$2bboeQ&F9-BE z>!9tlq3vKTJjS|_f~c0<2$t9(mLKZd^v$r-bsz=U2U#8Q6z2ZN z$6mP!94~Fg{9Qx4dur@5@S(mo)aT`hXh9vUfzIjQDxPe|dNy!1W&_A3^-CQ_bbmYY z%d*2N+j~uF&iGv{+v^YOvi7u{JoaJ8Q-xgW`JGQa z=V{UMbDTEge7Hl7(HI-myvT7<&W(p_;A7^oiX0Cy&Z9gLxz6LdV>9{-+dJo)rRX>Q z3R`OQ2>&4)k44D&lyP@kaXF@83~#pQBzr!}j+im#LHP`g_QHPXowC_$H1`^mb;Nia2W%b2k(X)PX09~P$|kEyHtykq$SZ)g zL=bBx?&J6jwl}gc55&HYJUOC^{`vK}x?Zr-1p(E_Yo~I70(35PdW$bM|r!6^G#?bBKV?}mQZK4luc#$`LnN_|J<{5dLhsMny_v)IoM zs9Sd%V_zhX53;5*`?$HD6DU6Gj&4T&4s;e`;z2Y>DVRHk+;cj3VF@V?_rtdaL4+y(olZt!svJ(bYl3;U*<1M*=tv% z&nf$eB|CL7pWJitV*I&eo=>he%!fTlGu)~7H`o`Ovb=4TamLaa=TPSJE4|knwdAF4 zP9yA?p7WMs>@fUH*n0S}{+>o06ZWc=m)Sd;wnBcBn%}ZH_#M&wralC}!bUZJFPVei zD$Vac#FSv$xBjQ7>8(esc3vYJe>s|Od7vAK;T`t_|!W&(THG>~~?ug|BeokGt^YF5KqAm$~q27jAXoOI`S* zE_{g#x47^s7hdVYD_q#;!pmLwVqn$F8vk~ zoCrH$0OIG%1303Ddyb;?5k; zgE{5AgHq-8r=5HA#RE7&*NDZic1W;vT?v~j@-lJ^=2KE{yY}?#yb5Jw7z>k-Vk@lk zlq%-QI40@Cxj!lyN5Z65ay%pjBjbX`aMCc99tPc?q`y3k2TD!Gx^Itudhec_clQd> zoi7|LX7&%1onZHs&c<-K^O}v}j*ZSO=|VPLbfU%lAEQ3OZ^zC%y7%sh_U^g;HuCEk zO6-DO?>LAPi-S(41cBk@;M`UbaJ6sa54H;|?; zd%y(q`-~aJGc?|Tv#uNIBg7~uHoW3EXapZeJOg@cjSw$@?ts4+Ys?<>gkI1ZMuivQ zKj9bRRnRvbAzlOZBHmr#=|%Vy@NlaT1Henx9Fe@i3Eq6$omV=Yp-yxX z<#9X4yH6F%*YdEOdp!UCvPMKrVl8?n-)wpncTGa{h~vGrC<8II#RC zgWtZ?phK8>!&Ut?Rpv|2dPW`28_ssIP&nJqLg8#@3x%^iE)=d#$IUMv+xJ50*$);9 zXMb5Joc(H{aQ452!r7nZGN%66kLNNbT#X0IXVnj0k=N^=;|I>Q&I-ryQxErE#_*eF zg`cv+`O(cB#^lfOZZ2cOIqu<0tXcfAEv)BXW<-~Kb~wk;g~EgK1F!k(zjlsr$02_) zOTGv^jQkvz7pgyw-wTCv{68z)*WkZzEyL&659fii(!+PH(rLZ5)60=Y~YnVmQ1W5yFg(Q|6%MqM`%F#iQz2Ac|1xAQpa^qzDZp zN&^xik)q|I2!UPb9(*3a<%JDm2*Y}X$wf$h%n_1Hi_oomqIV#_p}vv`$*(#>#k@SH z9!d|y`iop-hPW_Ba7q(=-ynGvDGtA<2<^|8A+&PyM0+TiALP18gbFDwl(h;84W`n4 zL;FLCzD(yvs^|lnk0BnOp%t1z-_cffQJvlkO3Z3SJi&POH?xa?X&7$_ET<#-F&@8P zrQHF*a2irtP8N7~C*wunK|29!(lQR3rMMB+qvOdBA8`tv-@h6I_R@6YA5%`A`SLqe z+EKX4hlc*NQCNQ4N;ki6rDgkJzT{=%$KXa*G7Z10rJY0s6HdzV4!|-Ub;G%jb{KAB zO#B=?Ogw&DNIPPURV*t_8P>$(+)P`r%4_<azMLX<0U7OuUESL4Ir#(}+Vm;Nr*d zqdFe-au+Ix*6@H=-A$b`K3VL;8j9aoQxC?NG>^f<#7iMwYMyvcTJiWTLl*JwGt58@ zzi(UdrV(!%@kXq8Mt|RH#Pe~zSLqVMp&;5Y9(n#Ptd`Hm^-pb6BQHiZ`FRXYztq-Tgzj$uCS#7{=SFobsPxv_IBVdgAYG#H*OhfQ|DM-2aJoYT{*YXyipd z@}hYQ*0?7i-+hQ@8xzK5kyY(7-n&*r6OWW_z?d^Pp)hk2zTz8fye z&k{7@R)7lV31C(+F~=dVQj1LQmglPok86EVo~M9~{+n>>?mNJfSK|+c`6*2)%<}w5 zIpsg`Kf%R4M=vPj{}hyajJq_R2IZc@;P-&JhcNL!f(*E4FnBdE_Xq~#z=+(-n)KWT zat~m_BaCn19>#BJg#RHh*YqZRKjT}t2+TG6X)W*PfZ3kdlAG{t^A~`*W}ndEe+kSr z`Y}+J_c$om@OOZ@MjqDqePFJMD;i&fd>seRAT;K;-){oHq48C~T;m$t3CuO^gbu$Em}}Ul zHQo!%HR~~rUp{8?bRs05+>T>Hsq9vVLi%;z9941WTc&qip7 z$ARs;9$o~FSmFNw9JTN-fa4Z^7nt(}nmG7fD9!*Etnj5?=*z;ZfJZF67Pw~NtANKX zd_C}lg+Bp2Y2n*|jd9=@05;<-@21EDPg(Kr2cEX@!%QDi9H@^`VENn$ZsKnN^H~%P z@pl;V77!VLczuzfeiuNjU(j{Tc=EBqI*eEtQ@_%px}E58dcg-0!X32@xP zZNLQ!Uj@vyuBwk^A`D!y!n=WOc|OT-4!SVBiz5zf=0CRgH1LR(eh9c`;V%G>Tlj0h z#;`m!;0Y`Id%%+xJ_$T!;ZwlV7XDXYf%4Fh-@gO5?#?g+SSlaepUw9rvN5?0jwObM1$Mc3L4a2Pp-r2^TLc4Q znba^)M+e?qVDB7D4Gj(+G$QQ0ZHJU#*WPWn?u;4K;4Jsqk~3a+KyrTDjxWVy<{^3m zaCItP~4esC{rpXLSa4k!oi9grUKx(JyBk1leh za-o>qt2FQZ&k8j;j$c}KAl2pFMQiW1)v?ObF-9ZU&mGA=U)33(8?`jG%fGxc5R8 zN8RYqxUzy3-MGGkx0{%uVD{Z6>S_ltnd62D`<@Yf?}c^6i7E*1d1&07VJ_iV=sFd1 zIZ4dAJi=TALLIVEvux@L5UEK(-iBh|tWm$X11+8Rw}8kCN*Yp`J1XYB1cvui$WqJh zqHYAyh2ym>7901KDY8SdjW)6C#HIDnX_- cwI-94^QQ9EZ5!&sk@_t!?we8AT1>|O3#jMl(f|Me literal 0 HcmV?d00001 diff --git a/Me_Lua/r13/lsqlite3.so b/Me_Lua/r13/lsqlite3.so new file mode 100644 index 0000000000000000000000000000000000000000..fa267c0f42d3419e1581739f4fc5fda64abe8760 GIT binary patch literal 53091 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 literal 0 HcmV?d00001 diff --git a/Me_Lua/r13/ltn12.lua b/Me_Lua/r13/ltn12.lua new file mode 100644 index 0000000..1014de2 --- /dev/null +++ b/Me_Lua/r13/ltn12.lua @@ -0,0 +1,305 @@ +----------------------------------------------------------------------------- +-- LTN12 - Filters, sources, sinks and pumps. +-- LuaSocket toolkit. +-- Author: Diego Nehab +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Declare module +----------------------------------------------------------------------------- +local string = require("string") +local table = require("table") +local base = _G +local _M = {} +if module then -- heuristic for exporting a global package table + ltn12 = _M +end +local filter,source,sink,pump = {},{},{},{} + +_M.filter = filter +_M.source = source +_M.sink = sink +_M.pump = pump + +-- 2048 seems to be better in windows... +_M.BLOCKSIZE = 2048 +_M._VERSION = "LTN12 1.0.3" + +----------------------------------------------------------------------------- +-- Filter stuff +----------------------------------------------------------------------------- +-- returns a high level filter that cycles a low-level filter +function filter.cycle(low, ctx, extra) + base.assert(low) + return function(chunk) + local ret + ret, ctx = low(ctx, chunk, extra) + return ret + end +end + +-- chains a bunch of filters together +-- (thanks to Wim Couwenberg) +function filter.chain(...) + local arg = {...} + local n = select('#',...) + local top, index = 1, 1 + local retry = "" + return function(chunk) + retry = chunk and retry + while true do + if index == top then + chunk = arg[index](chunk) + if chunk == "" or top == n then return chunk + elseif chunk then index = index + 1 + else + top = top+1 + index = top + end + else + chunk = arg[index](chunk or "") + if chunk == "" then + index = index - 1 + chunk = retry + elseif chunk then + if index == n then return chunk + else index = index + 1 end + else base.error("filter returned inappropriate nil") end + end + end + end +end + +----------------------------------------------------------------------------- +-- Source stuff +----------------------------------------------------------------------------- +-- create an empty source +local function empty() + return nil +end + +function source.empty() + return empty +end + +-- returns a source that just outputs an error +function source.error(err) + return function() + return nil, err + end +end + +-- creates a file source +function source.file(handle, io_err) + if handle then + return function() + local chunk = handle:read(_M.BLOCKSIZE) + if not chunk then handle:close() end + return chunk + end + else return source.error(io_err or "unable to open file") end +end + +-- turns a fancy source into a simple source +function source.simplify(src) + base.assert(src) + return function() + local chunk, err_or_new = src() + src = err_or_new or src + if not chunk then return nil, err_or_new + else return chunk end + end +end + +-- creates string source +function source.string(s) + if s then + local i = 1 + return function() + local chunk = string.sub(s, i, i+_M.BLOCKSIZE-1) + i = i + _M.BLOCKSIZE + if chunk ~= "" then return chunk + else return nil end + end + else return source.empty() end +end + +-- creates rewindable source +function source.rewind(src) + base.assert(src) + local t = {} + return function(chunk) + if not chunk then + chunk = table.remove(t) + if not chunk then return src() + else return chunk end + else + table.insert(t, chunk) + end + end +end + +-- chains a source with one or several filter(s) +function source.chain(src, f, ...) + if ... then f=filter.chain(f, ...) end + base.assert(src and f) + local last_in, last_out = "", "" + local state = "feeding" + local err + return function() + if not last_out then + base.error('source is empty!', 2) + end + while true do + if state == "feeding" then + last_in, err = src() + if err then return nil, err end + last_out = f(last_in) + if not last_out then + if last_in then + base.error('filter returned inappropriate nil') + else + return nil + end + elseif last_out ~= "" then + state = "eating" + if last_in then last_in = "" end + return last_out + end + else + last_out = f(last_in) + if last_out == "" then + if last_in == "" then + state = "feeding" + else + base.error('filter returned ""') + end + elseif not last_out then + if last_in then + base.error('filter returned inappropriate nil') + else + return nil + end + else + return last_out + end + end + end + end +end + +-- creates a source that produces contents of several sources, one after the +-- other, as if they were concatenated +-- (thanks to Wim Couwenberg) +function source.cat(...) + local arg = {...} + local src = table.remove(arg, 1) + return function() + while src do + local chunk, err = src() + if chunk then return chunk end + if err then return nil, err end + src = table.remove(arg, 1) + end + end +end + +----------------------------------------------------------------------------- +-- Sink stuff +----------------------------------------------------------------------------- +-- creates a sink that stores into a table +function sink.table(t) + t = t or {} + local f = function(chunk, err) + if chunk then table.insert(t, chunk) end + return 1 + end + return f, t +end + +-- turns a fancy sink into a simple sink +function sink.simplify(snk) + base.assert(snk) + return function(chunk, err) + local ret, err_or_new = snk(chunk, err) + if not ret then return nil, err_or_new end + snk = err_or_new or snk + return 1 + end +end + +-- creates a file sink +function sink.file(handle, io_err) + if handle then + return function(chunk, err) + if not chunk then + handle:close() + return 1 + else return handle:write(chunk) end + end + else return sink.error(io_err or "unable to open file") end +end + +-- creates a sink that discards data +local function null() + return 1 +end + +function sink.null() + return null +end + +-- creates a sink that just returns an error +function sink.error(err) + return function() + return nil, err + end +end + +-- chains a sink with one or several filter(s) +function sink.chain(f, snk, ...) + if ... then + local args = { f, snk, ... } + snk = table.remove(args, #args) + f = filter.chain(unpack(args)) + end + base.assert(f and snk) + return function(chunk, err) + if chunk ~= "" then + local filtered = f(chunk) + local done = chunk and "" + while true do + local ret, snkerr = snk(filtered, err) + if not ret then return nil, snkerr end + if filtered == done then return 1 end + filtered = f(done) + end + else return 1 end + end +end + +----------------------------------------------------------------------------- +-- Pump stuff +----------------------------------------------------------------------------- +-- pumps one chunk from the source to the sink +function pump.step(src, snk) + local chunk, src_err = src() + local ret, snk_err = snk(chunk, src_err) + if chunk and ret then return 1 + else return nil, src_err or snk_err end +end + +-- pumps all data from a source to a sink, using a step function +function pump.all(src, snk, step) + base.assert(src and snk) + step = step or pump.step + while true do + local ret, err = step(src, snk) + if not ret then + if err then return nil, err + else return 1 end + end + end +end + +return _M diff --git a/Me_Lua/r13/luci/controller/admin/network.lua b/Me_Lua/r13/luci/controller/admin/network.lua new file mode 100644 index 0000000..f86b91c --- /dev/null +++ b/Me_Lua/r13/luci/controller/admin/network.lua @@ -0,0 +1,611 @@ +--[[ +LuCI - Lua Configuration Interface + +Copyright 2008 Steven Barth +Copyright 2011 Jo-Philipp Wich + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +]]-- + +module("luci.controller.admin.network", package.seeall) + +function index() + local uci = require("luci.model.uci").cursor() + local page + + page = node("admin", "network") + page.target = firstchild() + page.title = _("Network") + page.order = 50 + page.index = true + +-- if page.inreq then + local has_switch = false + + uci:foreach("network", "switch", + function(s) + has_switch = true + return false + end) + + if has_switch then + page = node("admin", "network", "vlan") + page.target = cbi("admin_network/vlan") + page.title = _("Switch") + page.order = 20 + + page = entry({"admin", "network", "switch_status"}, call("switch_status"), nil) + page.leaf = true + end + + + local has_wifi = false + + uci:foreach("wireless", "wifi-device", + function(s) + has_wifi = true + return false + end) + + if has_wifi then + page = entry({"admin", "network", "wireless_join"}, call("wifi_join"), nil) + page.leaf = true + + page = entry({"admin", "network", "wireless_add"}, call("wifi_add"), nil) + page.leaf = true + + page = entry({"admin", "network", "wireless_delete"}, call("wifi_delete"), nil) + page.leaf = true + + page = entry({"admin", "network", "wireless_status"}, call("wifi_status"), nil) + page.leaf = true + + page = entry({"admin", "network", "getWifiSettings"}, call("getWifiSettings"), nil) + page.leaf = true + + page = entry({"admin", "network", "getConnectInfo"}, call("lease_status"), nil) + page.leaf = true + + page = entry({"admin", "network", "getUpLoadSpeed"}, call("getUpLoadSpeed"), nil) + page.leaf = true + page = entry({"admin", "network", "getWanInfo"}, call("getWanInfo"), nil) + page.leaf = true + page = entry({"admin", "network", "getLanDhcp"}, call("getLanDhcp"), nil) + page.leaf = true + + page = entry({"admin", "network", "wireless_reconnect"}, call("wifi_reconnect"), nil) + page.leaf = true + + page = entry({"admin", "network", "wireless_shutdown"}, call("wifi_shutdown"), nil) + page.leaf = true + + page = entry({"admin", "network", "wireless"}, arcombine(template("admin_network/wifi_overview"), cbi("admin_network/wifi")), _("Wifi"), 15) + page.leaf = true + page.subindex = true + + if page.inreq then + local wdev + local net = require "luci.model.network".init(uci) + for _, wdev in ipairs(net:get_wifidevs()) do + local wnet + for _, wnet in ipairs(wdev:get_wifinets()) do + entry( + {"admin", "network", "wireless", wnet:id()}, + alias("admin", "network", "wireless"), + wdev:name() .. ": " .. wnet:shortname() + ) + end + end + end + end + + + page = entry({"admin", "network", "iface_add"}, cbi("admin_network/iface_add"), nil) + page.leaf = true + + page = entry({"admin", "network", "iface_delete"}, call("iface_delete"), nil) + page.leaf = true + + page = entry({"admin", "network", "iface_status"}, call("iface_status"), nil) + page.leaf = true + + page = entry({"admin", "network", "iface_reconnect"}, call("iface_reconnect"), nil) + page.leaf = true + + page = entry({"admin", "network", "iface_shutdown"}, call("iface_shutdown"), nil) + page.leaf = true + + page = entry({"admin", "network", "network"}, arcombine(cbi("admin_network/network"), cbi("admin_network/ifaces")), _("Interfaces"), 10) + page.leaf = true + page.subindex = true + + if page.inreq then + uci:foreach("network", "interface", + function (section) + local ifc = section[".name"] + if ifc ~= "loopback" then + entry({"admin", "network", "network", ifc}, + true, ifc:upper()) + end + end) + end + + + if nixio.fs.access("/etc/config/dhcp") then + page = node("admin", "network", "dhcp") + page.target = cbi("admin_network/dhcp") + page.title = _("DHCP and DNS") + page.order = 30 + + page = entry({"admin", "network", "dhcplease_status"}, call("lease_status"), nil) + page.leaf = true + + page = node("admin", "network", "hosts") + page.target = cbi("admin_network/hosts") + page.title = _("Hostnames") + page.order = 40 + end + + page = node("admin", "network", "routes") + page.target = cbi("admin_network/routes") + page.title = _("Static Routes") + page.order = 50 + + page = node("admin", "network", "diagnostics") + page.target = template("admin_network/diagnostics") + page.title = _("Diagnostics") + page.order = 60 + + page = entry({"admin", "network", "diag_ping"}, call("diag_ping"), nil) + page.leaf = true + + page = entry({"admin", "network", "diag_nslookup"}, call("diag_nslookup"), nil) + page.leaf = true + + page = entry({"admin", "network", "diag_traceroute"}, call("diag_traceroute"), nil) + page.leaf = true + + page = entry({"admin", "network", "diag_ping6"}, call("diag_ping6"), nil) + page.leaf = true + + page = entry({"admin", "network", "diag_traceroute6"}, call("diag_traceroute6"), nil) + page.leaf = true +end + +function wifi_join() + local function param(x) + return luci.http.formvalue(x) + end + + local function ptable(x) + x = param(x) + return x and (type(x) ~= "table" and { x } or x) or {} + end + + local dev = param("device") + local ssid = param("join") + + if dev and ssid then + local cancel = (param("cancel") or param("cbi.cancel")) and true or false + + if cancel then + luci.http.redirect(luci.dispatcher.build_url("admin/network/wireless_join?device=" .. dev)) + else + local cbi = require "luci.cbi" + local tpl = require "luci.template" + local map = luci.cbi.load("admin_network/wifi_add")[1] + + if map:parse() ~= cbi.FORM_DONE then + tpl.render("header") + map:render() + tpl.render("footer") + end + end + else + luci.template.render("admin_network/wifi_join") + end +end + +function wifi_add() + local dev = luci.http.formvalue("device") + local ntm = require "luci.model.network".init() + + dev = dev and ntm:get_wifidev(dev) + + if dev then + local net = dev:add_wifinet({ + mode = "ap", + ssid = "OpenWrt", + encryption = "none" + }) + + ntm:save("wireless") + luci.http.redirect(net:adminlink()) + end +end + +function wifi_delete(network) + local ntm = require "luci.model.network".init() + local wnet = ntm:get_wifinet(network) + if wnet then + local dev = wnet:get_device() + local nets = wnet:get_networks() + if dev then + ntm:del_wifinet(network) + ntm:commit("wireless") + local _, net + for _, net in ipairs(nets) do + if net:is_empty() then + ntm:del_network(net:name()) + ntm:commit("network") + end + end + luci.sys.call("env -i /bin/ubus call network reload >/dev/null 2>/dev/null") + luci.sys.call("env -i /sbin/wifi reload >/dev/null 2>/dev/null") + end + end + + luci.http.redirect(luci.dispatcher.build_url("admin/network/wireless")) +end + +function iface_status(ifaces) + local netm = require "luci.model.network".init() + local rv = { } + + local iface + for iface in ifaces:gmatch("[%w%.%-_]+") do + local net = netm:get_network(iface) + local device = net and net:get_interface() + if device then + local data = { + id = iface, + proto = net:proto(), + uptime = net:uptime(), + gwaddr = net:gwaddr(), + dnsaddrs = net:dnsaddrs(), + name = device:shortname(), + type = device:type(), + ifname = device:name(), + macaddr = device:mac(), + is_up = device:is_up(), + rx_bytes = device:rx_bytes(), + tx_bytes = device:tx_bytes(), + rx_packets = device:rx_packets(), + tx_packets = device:tx_packets(), + + ipaddrs = { }, + ip6addrs = { }, + subdevices = { } + } + + local _, a + for _, a in ipairs(device:ipaddrs()) do + data.ipaddrs[#data.ipaddrs+1] = { + addr = a:host():string(), + netmask = a:mask():string(), + prefix = a:prefix() + } + end + for _, a in ipairs(device:ip6addrs()) do + if not a:is6linklocal() then + data.ip6addrs[#data.ip6addrs+1] = { + addr = a:host():string(), + netmask = a:mask():string(), + prefix = a:prefix() + } + end + end + + for _, device in ipairs(net:get_interfaces() or {}) do + data.subdevices[#data.subdevices+1] = { + name = device:shortname(), + type = device:type(), + ifname = device:name(), + macaddr = device:mac(), + macaddr = device:mac(), + is_up = device:is_up(), + rx_bytes = device:rx_bytes(), + tx_bytes = device:tx_bytes(), + rx_packets = device:rx_packets(), + tx_packets = device:tx_packets(), + } + end + + rv[#rv+1] = data + else + rv[#rv+1] = { + id = iface, + name = iface, + type = "ethernet" + } + end + end + + if #rv > 0 then + luci.http.prepare_content("application/json") + luci.http.write_json(rv) + return + end + + luci.http.status(404, "No such device") +end + +function iface_reconnect(iface) + local netmd = require "luci.model.network".init() + local net = netmd:get_network(iface) + if net then + luci.sys.call("env -i /sbin/ifup %q >/dev/null 2>/dev/null" % iface) + luci.http.status(200, "Reconnected") + return + end + + luci.http.status(404, "No such interface") +end + +function iface_shutdown(iface) + local netmd = require "luci.model.network".init() + local net = netmd:get_network(iface) + if net then + luci.sys.call("env -i /sbin/ifdown %q >/dev/null 2>/dev/null" % iface) + luci.http.status(200, "Shutdown") + return + end + + luci.http.status(404, "No such interface") +end + +function iface_delete(iface) + local netmd = require "luci.model.network".init() + local net = netmd:del_network(iface) + if net then + luci.sys.call("env -i /sbin/ifdown %q >/dev/null 2>/dev/null" % iface) + luci.http.redirect(luci.dispatcher.build_url("admin/network/network")) + netmd:commit("network") + netmd:commit("wireless") + return + end + + luci.http.status(404, "No such interface") +end + +function wifi_status(devs) + local s = require "luci.tools.status" + local rv = { } + + local dev + for dev in devs:gmatch("[%w%.%-]+") do + rv[#rv+1] = s.wifi_network(dev) + end + + if #rv > 0 then + luci.http.prepare_content("application/json") + luci.http.write_json(rv) + return + end + + luci.http.status(404, "No such device") +end + +--wifi重连 &开关基础函数 +local function wifi_reconnect_shutdown(shutdown, wnet) + local netmd = require "luci.model.network".init() + local net = netmd:get_wifinet(wnet) + local dev = net:get_device() + if dev and net then + dev:set("disabled", nil) + net:set("disabled", shutdown and 1 or nil) + netmd:commit("wireless") + + luci.sys.call("env -i /bin/ubus call network reload >/dev/null 2>/dev/null") + + luci.sys.call("env -i /sbin/wifi reload >/dev/null 2>/dev/null") + + luci.http.status(200, shutdown and "Shutdown" or "Reconnected") + + return + end + + luci.http.status(404, "No such radio") +end +--wifi重连 +function wifi_reconnect(wnet) + wifi_reconnect_shutdown(false, wnet) +end +--wifi开关 +function wifi_shutdown(wnet) + wifi_reconnect_shutdown(true, wnet) +end + +function wifiNetworks() + local result = {} + local network = require "luci.model.network".init() + local dev + for _, dev in ipairs(network:get_wifidevs()) do + local rd = { + up = dev:is_up(), + device = dev:name(), + name = dev:get_i18n(), + networks = {} + } + local wifiNet + for _, wifiNet in ipairs(dev:get_wifinets()) do + rd.networks[#rd.networks+1] = { + name = wifiNet:shortname(), + up = wifiNet:is_up(), + mode = wifiNet:active_mode(), + ssid = wifiNet:active_ssid(), + bssid = wifiNet:active_bssid(), + encryption = wifiNet:active_encryption(), + frequency = wifiNet:frequency(), + channel = wifiNet:channel(), + signal = wifiNet:signal(), + quality = wifiNet:signal_percent(), + noise = wifiNet:noise(), + bitrate = wifiNet:bitrate(), + ifname = wifiNet:ifname(), + assoclist = wifiNet:assoclist(), + country = wifiNet:country(), + txpower = wifiNet:txpower(), + txpoweroff = wifiNet:txpower_offset(), + key = wifiNet:get("key"), + key1 = wifiNet:get("key1"), + encryption_src = wifiNet:get("encryption"), + hidden = wifiNet:get("hidden"), + } + end + result[#result+1] = rd + end + return result +end + +function wifiNetwork(wifiDeviceName) + local network = require "luci.model.network".init() + local wifiNet = network:get_wifinet(wifiDeviceName) + if wifiNet then + local dev = wifiNet:get_device() + if dev then + return { + id = wifiDeviceName, + name = wifiNet:shortname(), + up = wifiNet:is_up(), + mode = wifiNet:active_mode(), + ssid = wifiNet:active_ssid(), + bssid = wifiNet:active_bssid(), + encryption = wifiNet:active_encryption(), + encryption_src = wifiNet:get("encryption"), + frequency = wifiNet:frequency(), + channel = wifiNet:channel(), + signal = wifiNet:signal(), + quality = wifiNet:signal_percent(), + noise = wifiNet:noise(), + bitrate = wifiNet:bitrate(), + ifname = wifiNet:ifname(), + assoclist = wifiNet:assoclist(), + country = wifiNet:country(), + txpower = wifiNet:txpower(), + txpoweroff = wifiNet:txpower_offset(), + key = wifiNet:get("key"), + key1 = wifiNet:get("key1"), + hidden = wifiNet:get("hidden"), + device = { + up = dev:is_up(), + device = dev:name(), + name = dev:get_i18n() + } + } + end + end + return {} +end + +function getWifiSettings() + local infoList = {} + local wifis = wifiNetworks() + for i,wifiNet in ipairs(wifis) do + local item = {} + local index = 1 + if wifiNet["up"] then + item["status"] = "1" + else + item["status"] = "0" + end + local encryption = wifiNet.networks[index].encryption_src + local key = wifiNet.networks[index].key + if encryption == "wep-open" then + key = wifiNet.networks[index].key1 + if key:len()>4 and key:sub(0,2)=="s:" then + key = key:sub(3) + end + end + local channel = wifiNet.networks[index].cchannel + -- local channelparseinfo = channelHelper(channel) + item["ifname"] = wifiNet.networks[index].ifname + item["device"] = wifiNet.device..".network"..index + item["ssid"] = wifiNet.networks[index].ssid + -- item["channel"] = channelparseinfo.channel + -- item["channelInfo"] = getBandList(channel) + -- item["channelInfo"]["channel"] = wifiNet.networks[index].channel + item["mode"] = wifiNet.networks[index].mode + item["hidden"] = wifiNet.networks[index].hidden or 0 + item["signal"] = wifiNet.networks[index].signal + item["password"] = key + item["encryption"] = encryption + infoList[#wifis+1-i] = item + end + --local guestwifi = getGuestWifi(1) + -- if guestwifi then + -- table.insert(infoList, guestwifi) + -- end + --return infoList + local result = {} + -- local code = 0 + -- result["info"] = infoList + -- result["code"] = code + luci.http.write_json(infoList) +end + +function lease_status() + local s = require "luci.tools.status" + + luci.http.prepare_content("application/json") + luci.http.write('[') + luci.http.write_json(s.dhcp_leases()) + luci.http.write(',') + luci.http.write_json(s.dhcp6_leases()) + luci.http.write(']') +end + +function switch_status(switches) + local s = require "luci.tools.status" + + luci.http.prepare_content("application/json") + luci.http.write_json(s.switch_status(switches)) +end + +function diag_command(cmd, addr) + if addr and addr:match("^[a-zA-Z0-9%-%.:_]+$") then + luci.http.prepare_content("text/plain") + + local util = io.popen(cmd % addr) + if util then + while true do + local ln = util:read("*l") + if not ln then break end + luci.http.write(ln) + luci.http.write("\n") + end + + util:close() + end + + return + end + + luci.http.status(500, "Bad address") +end + +function diag_ping(addr) + diag_command("ping -c 5 -W 1 %q 2>&1", addr) +end + +function diag_traceroute(addr) + diag_command("traceroute -q 1 -w 1 -n %q 2>&1", addr) +end + +function diag_nslookup(addr) + diag_command("nslookup %q 2>&1", addr) +end + +function diag_ping6(addr) + diag_command("ping6 -c 5 %q 2>&1", addr) +end + +function diag_traceroute6(addr) + diag_command("traceroute6 -q 1 -w 2 -n %q 2>&1", addr) +end + diff --git a/Me_Lua/r13/luci/controller/api/index.lua b/Me_Lua/r13/luci/controller/api/index.lua new file mode 100644 index 0000000..138d23b --- /dev/null +++ b/Me_Lua/r13/luci/controller/api/index.lua @@ -0,0 +1,238 @@ +module("luci.controller.api.index", package.seeall) +--import lua_file,路径问题? +local bfs = require "meizu.bfs" +local cjson = require "cjson" +local lfs = require "lfs" +local lue = require("luci.util").exec +local nwfs = require "meizu.nwfs" +local RC = require "meizu.r10config" +local sipfs = require "meizu.sipfs" +local upgdfs = require "meizu.upgdfs" + +--replace +--bind_router流程如何? +bind_router = bfs.bind_router +data_to_json = bfs.data_to_json +exec_cmd_in_sh = bfs.exec_cmd_in_sh +exec_reboot = bfs.exec_reboot +factory_reset = bfs.factory_reset +set_passwd = bfs.set_passwd +silent_upgrade = bfs.silent_upgrade + +sip = sipfs.sip +pysip = sipfs.pysip +upload_router_log = sipfs.upload_router_log +--network +nw_check_sys_password = nwfs.nw_check_sys_password +nw_get_connect_device_list = nwfs.nw_get_connect_device_list +nw_get_device_details = nwfs.nw_get_device_details +nw_get_wan_type = nwfs.nw_get_wan_type +nw_get_wifi_settings = nwfs.nw_get_wifi_settings +nw_set_device_name = nwfs.nw_set_device_name +nw_set_wan_switch = nwfs.nw_set_wan_switch +nw_set_wan_type = nwfs.nw_set_wan_type +nw_wifi_settings = nwfs.nw_wifi_settings +nw_get_wireless_channel = nwfs.nw_get_wireless_channel +nw_set_wireless_channel = nwfs.nw_set_wireless_channel + +nw_scan_ble_switch = nwfs.nw_scan_ble_switch +nw_get_ble_device_list = nwfs.nw_get_ble_device_list +nw_add_ble_mesh_device = nwfs.nw_add_ble_mesh_device +nw_get_ble_device_status = nwfs.nw_get_ble_device_status +nw_get_mesh_device_list = nwfs.nw_get_mesh_device_list +nw_remove_ble_from_mesh = nwfs.nw_remove_ble_from_mesh +nw_dismiss_mesh = nwfs.nw_dismiss_mesh +nw_set_mesh_device_attr = nwfs.nw_set_mesh_device_attr +nw_reboot_mesh_device = nwfs.nw_reboot_mesh_device +nw_unmesh_all_device = nwfs.nw_unmesh_all_device +nw_set_mesh_device_timer = nwfs.nw_set_mesh_device_timer +nw_del_mesh_device_timer = nwfs.nw_del_mesh_device_timer +nw_set_mesh_network_pwd = nwfs.nw_set_mesh_network_pwd +nw_set_lamp_brightness = nwfs.nw_set_lamp_brightness + +get_net_device = nwfs.get_net_device +real_time_net_speed = nwfs.real_time_net_speed + +check_upgrade = upgdfs.check_upgrade +do_upgrade = upgdfs.do_upgrade +local_upgrade = upgdfs.local_upgrade + +function index() + --nw: abridged for "Nei Wang"; ww abridged for "Wai Wang" + --?? + local root = node() + if not root.target then + root.target = alias("api") + root.index = true + end + local page = node("api") + + --? + page.target = firstchild() + page.title = _("api") + page.order = 10 + page.index = true + page.sysauth = "root" + page.sysauth_authenticator = "htmlauth" + + page = entry({"api", "searchrouter"}, call("nw_search_router"), nil) + --leaf属性具体? + page.leaf = true + + page = entry({"api", "bindRouter"}, call("bind_router"), nil, nil) + page.leaf = true + + page = entry({"api", "sip"}, call("sip"), nil, nil) + page.leaf = true + page = entry({"api", "pysip"}, call("pysip"), nil, nil) + page.leaf = true + + page = entry({"api", "getWifiSettings"}, call("nw_get_wifi_settings"), nil) + page.leaf = true + page = entry({"api", "getConnectDeviceList"}, call("nw_get_connect_device_list"), nil) + page.leaf = true + page = entry({"api", "getdevicedetails"}, call("nw_get_device_details"), nil) + page.leaf = true + page = entry({"api", "getNetDevice"}, call("get_net_device"), nil) + page.leaf = true + page = entry({"api", "getWanType"}, call("nw_get_wan_type"), nil) + page.leaf = true + page = entry({"api", "realtimenetspeed"}, call("nw_real_time_net_speed"), nil, nil) + page.leaf = true + page = entry({"api", "setWanType"}, call("nw_set_wan_type"), nil) + page.leaf = true + page = entry({"api", "setDeviceName"}, call("nw_set_device_name"), nil) + page.leaf = true + page = entry({"api", "setWanSwitch"}, call("nw_set_wan_switch"), nil) + page.leaf = true + page = entry({"api", "wifiSettings"}, call("nw_wifi_settings"), nil) + page.leaf = true + page = entry({"api", "getWirelessChannel"}, call("nw_get_wireless_channel"), nil) + page.leaf = true + page = entry({"api", "setWirelessChannel"}, call("nw_set_wireless_channel"), nil) + page.leaf = true + + page = entry({"api", "factoryreset"}, call("factory_reset"), nil, nil) + page.leaf = true + page = entry({"api", "reboot"}, call("nw_exec_reboot"), nil, nil) + page.leaf = true + page = entry({"api", "localupgrade"}, call("local_upgrade"), nil, nil) + page.leaf = true + page = entry({"api", "silentupgrade"}, call("silent_upgrade"), nil, nil) + page.leaf = true + + page = entry({"api", "checkSysPassword"}, call("nw_check_sys_password"), nil) + page.leaf = true + page = entry({"api", "setpasswd"}, call("set_passwd"), nil, nil) + page.leaf = true + + page = entry({"api", "doupgrade"}, call("nw_do_upgrade"), nil) + page.leaf = true + page = entry({"api", "checkupgrade"}, call("nw_check_upgrade"), nil) + page.leaf = true + + page = entry({"api", "scanBleSwitch"}, call("nw_scan_ble_switch"), nil) + page.leaf = true + page = entry({"api", "getBleDeviceList"}, call("nw_get_ble_device_list"), nil) + page.leaf = true + page = entry({"api", "addMeshDevice"}, call("nw_add_ble_mesh_device"), nil) + page.leaf = true + page = entry({"api", "removeBleFromMesh"}, call("nw_remove_ble_from_mesh"), nil) + page.leaf = true + page = entry({"api", "getMeshDeviceDetail"}, call("nw_get_ble_device_status"), nil) + page.leaf = true + page = entry({"api", "getMeshDeviceList"}, call("nw_get_mesh_device_list"), nil) + page.leaf = true + page = entry({"api", "dismissMesh"}, call("nw_dismiss_mesh"), nil) + page.leaf = true + page = entry({"api", "setMeshDeviceAttr"}, call("nw_set_mesh_device_attr"), nil) + page.leaf = true + page = entry({"api", "rebootMeshDevice"}, call("nw_reboot_mesh_device"), nil) + page.leaf = true + page = entry({"api", "unmeshAllDevice"}, call("nw_unmesh_all_device"), nil) + page.leaf = true + page = entry({"api", "setMeshDeviceTimer"}, call("nw_set_mesh_device_timer"), nil) + page.leaf = true + page = entry({"api", "delMeshDeviceTimer"}, call("nw_del_mesh_device_timer"), nil) + page.leaf = true + page = entry({"api", "setMeshNetWorkPassword"}, call("nw_set_mesh_network_pwd"), nil) + page.leaf = true + page = entry({"api", "setLampBrightness"}, call("nw_set_lamp_brightness"), nil) + page.leaf = true +end +--验证升级 +function nw_check_upgrade() + local ret = check_upgrade() + luci.http.write(ret) --输出到html页面,结果? +end + +--升级,nw全称是? +function nw_do_upgrade() + local ret = {} --这是一个数组还是哈希? + luci.http.status(200, "upgrading....") + ret["code"] = 2004 + ret["result"] = "upgrading...." + luci.http.write(data_to_json(ret)) --把ret转成json并输出到页面? + do_upgrade() +end +--实时网速 +function nw_real_time_net_speed() + --这句是做啥? + luci.http.prepare_content("application/json") + + local result = real_time_net_speed() + luci.http.write_json(result) +end +--执行重刷固件? +function nw_exec_reboot() + local ret = {} + ret["result"] = true + luci.http.write_json(ret) + exec_reboot() +end +--搜索路由器具体功能实现? +function nw_search_router() + local wl_type_val = luci.http.formvalue("type") + local wireless_dev = "ra0" + local wireless_type = "2.4G" + if wl_type_val == "2" then + wireless_type = "5G" + wireless_dev = "rai0" + end + --MZLog.log?? + require "MZLog".log(3, wl_type_val) + require "MZLog".log(3, wireless_type) + local sub = require "string".sub + local trim = require "string".trim + local ssid_table = {} + local cmd = [[ap_scan.sh ]]..wireless_dev..[[| grep "^[0-9]"]] + local ssids = io.popen(cmd) + local ln = 1 + require "MZLog".log(3, debug.getinfo(1).currentline) + for line in ssids:lines() do + require "MZLog".log(3, debug.getinfo(1).currentline) + require "MZLog".log(3, line) + require "MZLog".log(3, debug.getinfo(1).currentline) + local assid = {} + + local channel = trim(sub(line, 1, 4)) + local ssid = trim(sub(line, 5, 37)) + local mac = trim(sub(line, 38, 54)) + local sec = trim(sub(line, 58, 79)) + local rss = trim(sub(line, 81, 82)) + local extch = trim(sub(line, 98, 103)) + assid["mac"] = mac + assid["rss"] = rss + assid["sec"] = sec + assid["ssid"] = ssid + assid["type"] = wireless_type + assid["channel"] = channel + assid["extch"] = extch + + ssid_table[ln] = assid + ln = ln + 1 + end + ssids:close() + require "MZLog".log(3, debug.getinfo(1).currentline) + luci.http.write_json(ssid_table) +end diff --git a/Me_Lua/r13/luci/controller/bs/index.lua b/Me_Lua/r13/luci/controller/bs/index.lua new file mode 100644 index 0000000..4f0a114 --- /dev/null +++ b/Me_Lua/r13/luci/controller/bs/index.lua @@ -0,0 +1,105 @@ +module("luci.controller.bs.index", package.seeall) + + +local arpmon = require "meizu.arpmon" +local bfs = require "meizu.bfs" + +new_device_notify = arpmon.new_device_notify + +function index() + local root = node() + if not root.target then + root.target = alias("bs") + root.index = true + end + local page = node("bs") + --page.target = firstchild() + page.title = _("bs") + page.order = 10 + page.index = true + page = entry({"bs", "info"}, call("info"), nil, nil) + page.leaf = true + page = entry({"bs", "token"}, call("token"), nil, nil) + page.leaf = true + + page = entry({"bs", "newdevicenotify"}, call("new_device_notify"), nil) + page.leaf = true + + page = entry({"bs", "devip"}, call("devip"), nil, nil) + page.leaf = true + page = entry({"bs", "testip"}, call("testip"), nil, nil) + page.leaf = true + page = entry({"bs", "normip"}, call("normip"), nil, nil) + page.leaf = true + + page = entry({"bs", "apk"}, call("apk"), nil) + page.leaf = true +end + +function info() + luci.http.prepare_content("application/json") + local result = bfs.sysinfo() + luci.http.write_json(result) +end + +function token() + luci.http.prepare_content("application/json") + local sauth = require "luci.sauth" + local token = sauth.noAuthGetToken() + if token then + luci.http.write_json(token) + end +end + +function show_hosts() + local lue = require"luci.util".exec + local cmd = "cat /etc/hosts" + local ret = lue(cmd) + luci.http.write(ret) +end + +function devip() + local lue = require"luci.util".exec + local cmd = "/usr/sbin/mzrts_ips.sh devip" + local ret = lue(cmd) + show_hosts() +end + +function normip() + local lue = require"luci.util".exec + local cmd = "/usr/sbin/mzrts_ips.sh" + local ret = lue(cmd) + show_hosts() +end + +function testip() + local lue = require"luci.util".exec + local cmd = "/usr/sbin/mzrts_ips.sh testip" + local ret = lue(cmd) + show_hosts() +end + +function apk() + local fn, fd, block + local cmd = "ls /www/apk_download/apk/*.apk | awk '{printf $1}'" + fd = io.popen(cmd) + fn = fd:read("*l") + fd:close() + if fn ~= nil then + fd = nixio.open(fn, "r") + luci.http.header('Content-Disposition', 'attachment; filename="%s"' % {nixio.fs.basename(fn)}) + luci.http.prepare_content("application/octet-stream") + while true do + block = fd:read(nixio.const.buffersize) + require "MZLog".log(3, debug.getinfo(1).currentline) + if (not block) or (#block == 0) then + require "MZLog".log(3, debug.getinfo(1).currentline) + break + else + luci.http.write(block) + end + end + fd:close() + end + luci.http.close() +end diff --git a/Me_Lua/r13/luci/dispatcher.lua b/Me_Lua/r13/luci/dispatcher.lua new file mode 100644 index 0000000..a8b7fd1 --- /dev/null +++ b/Me_Lua/r13/luci/dispatcher.lua @@ -0,0 +1,965 @@ +--[[ +LuCI - Dispatcher + +Description: +The request dispatcher and module dispatcher generators + +FileId: +$Id$ + +License: +Copyright 2008 Steven Barth + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +]]-- + +--- LuCI web dispatcher. +local fs = require "nixio.fs" +local sys = require "luci.sys" +local init = require "luci.init" +local util = require "luci.util" +local http = require "luci.http" +local nixio = require "nixio", require "nixio.util" + +module("luci.dispatcher", package.seeall) +context = util.threadlocal() +uci = require "luci.model.uci" +i18n = require "luci.i18n" +_M.fs = fs + +authenticator = {} + +-- Index table +local index = nil + +-- Fastindex +local fi + + +--- Build the URL relative to the server webroot from given virtual path. +-- @param ... Virtual path +-- @return Relative URL +function build_url(...) + local path = {...} + local url = { http.getenv("SCRIPT_NAME") or "" } + + local k, v + for k, v in pairs(context.urltoken) do + url[#url+1] = "/;" + url[#url+1] = http.urlencode(k) + url[#url+1] = "=" + url[#url+1] = http.urlencode(v) + end + + local p + for _, p in ipairs(path) do + if p:match("^[a-zA-Z0-9_%-%.%%/,;]+$") then + url[#url+1] = "/" + url[#url+1] = p + end + end + + return table.concat(url, "") +end + +--- Check whether a dispatch node shall be visible +-- @param node Dispatch node +-- @return Boolean indicating whether the node should be visible +function node_visible(node) + if node then + return not ( + (not node.title or #node.title == 0) or + (not node.target or node.hidden == true) or + (type(node.target) == "table" and node.target.type == "firstchild" and + (type(node.nodes) ~= "table" or not next(node.nodes))) + ) + end + return false +end + +--- Return a sorted table of visible childs within a given node +-- @param node Dispatch node +-- @return Ordered table of child node names +function node_childs(node) + local rv = { } + if node then + local k, v + for k, v in util.spairs(node.nodes, + function(a, b) + return (node.nodes[a].order or 100) + < (node.nodes[b].order or 100) + end) + do + if node_visible(v) then + rv[#rv+1] = k + end + end + end + return rv +end + + +--- Send a 404 error code and render the "error404" template if available. +-- @param message Custom error message (optional) +-- @return false +function error404(message) + luci.http.status(404, "Not Found") + message = message or "Not Found" + + require("luci.template") + if not luci.util.copcall(luci.template.render, "error404") then + luci.http.prepare_content("text/plain") + luci.http.write(message) + end + return false +end + +--- Send a 500 error code and render the "error500" template if available. +-- @param message Custom error message (optional)# +-- @return false +function error500(message) + luci.util.perror(message) + if not context.template_header_sent then + luci.http.status(500, "Internal Server Error") + luci.http.prepare_content("text/plain") + luci.http.write(message) + else + require("luci.template") + if not luci.util.copcall(luci.template.render, "error500", {message=message}) then + luci.http.prepare_content("text/plain") + luci.http.write(message) + end + end + return false +end + +function authenticator.htmlauth(validator, accs, default) + local user = luci.http.formvalue("username") + local pass = luci.http.formvalue("password") + + if user and validator(user, pass) then + return user + end + + require("luci.i18n") + require("luci.template") + context.path = {} + luci.template.render("sysauth", {duser=default, fuser=user}) + return false + +end + +--- Dispatch an HTTP request. +-- @param request LuCI HTTP Request object +function httpdispatch(request, prefix) + luci.http.context.request = request + + local r = {} + context.request = r + context.urltoken = {} + + local pathinfo = http.urldecode(request:getenv("PATH_INFO") or "", true) + + if prefix then + for _, node in ipairs(prefix) do + r[#r+1] = node + end + end + + local tokensok = true + for node in pathinfo:gmatch("[^/]+") do + local tkey, tval + if tokensok then + tkey, tval = node:match(";(%w+)=([a-fA-F0-9]*)") + end + if tkey then + context.urltoken[tkey] = tval + else + tokensok = false + r[#r+1] = node + end + end + + local stat, err = util.coxpcall(function() + dispatch(context.request) + end, error500) + + luci.http.close() + + --context._disable_memtrace() +end + +--- Dispatches a LuCI virtual path. +-- @param request Virtual path +function dispatch(request) + --context._disable_memtrace = require "luci.debug".trap_memtrace("l") + require "MZLog".log(3, request) + local ctx = context + ctx.path = request + + local conf = require "luci.config" + assert(conf.main, + "/etc/config/luci seems to be corrupt, unable to find section 'main'") + + local lang = conf.main.lang or "auto" + if lang == "auto" then + lang = "zh_cn" + --[[ + [local aclang = http.getenv("HTTP_ACCEPT_LANGUAGE") or "" + [for lpat in aclang:gmatch("[%w-]+") do + [ lpat = lpat and lpat:gsub("-", "_") + [ if conf.languages[lpat] then + [ lang = lpat + [ break + [ end + [end + ]] + end + require "luci.i18n".setlanguage(lang) + + local c = ctx.tree + local stat + if not c then + c = createtree() + end + + local track = {} + local args = {} + ctx.args = args + ctx.requestargs = ctx.requestargs or args + local n + local token = ctx.urltoken + local preq = {} + local freq = {} + + for i, s in ipairs(request) do + preq[#preq+1] = s + freq[#freq+1] = s + c = c.nodes[s] + n = i + if not c then + break + end + + util.update(track, c) + + if c.leaf then + break + end + end + + if c and c.leaf then + for j=n+1, #request do + args[#args+1] = request[j] + freq[#freq+1] = request[j] + end + end + + ctx.requestpath = ctx.requestpath or freq + ctx.path = preq + + if track.i18n then + i18n.loadc(track.i18n) + end + + -- Init template engine + if (c and c.index) or not track.notemplate then + local tpl = require("luci.template") + local media = track.mediaurlbase or luci.config.main.mediaurlbase + if not pcall(tpl.Template, "themes/%s/header" % fs.basename(media)) then + media = nil + for name, theme in pairs(luci.config.themes) do + if name:sub(1,1) ~= "." and pcall(tpl.Template, + "themes/%s/header" % fs.basename(theme)) then + media = theme + end + end + assert(media, "No valid theme found") + end + + local function _ifattr(cond, key, val) + if cond then + local env = getfenv(3) + local scope = (type(env.self) == "table") and env.self + return string.format( + ' %s="%s"', tostring(key), + luci.util.pcdata(tostring( val + or (type(env[key]) ~= "function" and env[key]) + or (scope and type(scope[key]) ~= "function" and scope[key]) + or "" )) + ) + else + return '' + end + end + + tpl.context.viewns = setmetatable({ + write = luci.http.write; + include = function(name) tpl.Template(name):render(getfenv(2)) end; + translate = i18n.translate; + translatef = i18n.translatef; + export = function(k, v) if tpl.context.viewns[k] == nil then tpl.context.viewns[k] = v end end; + striptags = util.striptags; + pcdata = util.pcdata; + media = media; + theme = fs.basename(media); + resource = luci.config.main.resourcebase; + ifattr = function(...) return _ifattr(...) end; + attr = function(...) return _ifattr(true, ...) end; + }, {__index=function(table, key) + if key == "controller" then + return build_url() + elseif key == "REQUEST_URI" then + return build_url(unpack(ctx.requestpath)) + else + return rawget(table, key) or _G[key] + end + end}) + end + + track.dependent = (track.dependent ~= false) + assert(not track.dependent or not track.auto, + "Access Violation\nThe page at '" .. table.concat(request, "/") .. "/' " .. + "has no parent node so the access to this location has been denied.\n" .. + "This is a software bug, please report this message at " .. + "http://luci.subsignal.org/trac/newticket" + ) + + local isremote = http.getenv("REMOTE_ADDR") == "127.0.0.1" + + if not isremote and track.sysauth then + local sauth = require "luci.sauth" + + local authen = type(track.sysauth_authenticator) == "function" + and track.sysauth_authenticator + or authenticator[track.sysauth_authenticator] + + local def = (type(track.sysauth) == "string") and track.sysauth + local accs = def and {track.sysauth} or track.sysauth + local sess = ctx.authsession + local verifytoken = false + if not sess then + sess = luci.http.getcookie("sysauth") + sess = sess and sess:match("^[a-f0-9]*$") + verifytoken = true + end + + local sdat = sauth.read(sess) + local user + + if sdat then + if not verifytoken or ctx.urltoken.stok == sdat.token then + user = sdat.user + end + else + local eu = http.getenv("HTTP_AUTH_USER") + local ep = http.getenv("HTTP_AUTH_PASS") + if eu and ep and luci.sys.user.checkpasswd(eu, ep) then + authen = function() return eu end + end + end + + if not util.contains(accs, user) then + if authen then + ctx.urltoken.stok = nil + local user, sess = authen(luci.sys.user.checkpasswd, accs, def) + if not user or not util.contains(accs, user) then + return + else + local sid = sess or luci.sys.uniqueid(16) + if not sess then + local token = luci.sys.uniqueid(16) + sauth.reap() + sauth.write(sid, { + user=user, + token=token, + secret=luci.sys.uniqueid(16) + }) + ctx.urltoken.stok = token + end + luci.http.header("Set-Cookie", "sysauth=" .. sid.."; path="..build_url()) + ctx.authsession = sid + ctx.authuser = user + end + else + luci.http.status(403, "Forbidden") + return + end + else + ctx.authsession = sess + ctx.authuser = user + end + end + + if track.setgroup then + luci.sys.process.setgroup(track.setgroup) + end + + if track.setuser then + luci.sys.process.setuser(track.setuser) + end + + local target = nil + if c then + if type(c.target) == "function" then + target = c.target + elseif type(c.target) == "table" then + target = c.target.target + end + end + + if c and (c.index or type(target) == "function") then + ctx.dispatched = c + ctx.requested = ctx.requested or ctx.dispatched + end + + if c and c.index then + local tpl = require "luci.template" + + if util.copcall(tpl.render, "indexer", {}) then + return true + end + end + + if type(target) == "function" then + util.copcall(function() + local oldenv = getfenv(target) + local module = require(c.module) + local env = setmetatable({}, {__index= + + function(tbl, key) + return rawget(tbl, key) or module[key] or oldenv[key] + end}) + + setfenv(target, env) + end) + + local ok, err + if type(c.target) == "table" then + ok, err = util.copcall(target, c.target, unpack(args)) + else + ok, err = util.copcall(target, unpack(args)) + end + assert(ok, + "Failed to execute " .. (type(c.target) == "function" and "function" or c.target.type or "unknown") .. + " dispatcher target for entry '/" .. table.concat(request, "/") .. "'.\n" .. + "The called action terminated with an exception:\n" .. tostring(err or "(unknown)")) + else + local root = node() + if not root or not root.target then + error404("No root node was registered, this usually happens if no module was installed.\n" .. + "Install luci-mod-admin-full and retry. " .. + "If the module is already installed, try removing the /tmp/luci-indexcache file.") + else + error404("No page is registered at '/" .. table.concat(request, "/") .. "'.\n" .. + "If this url belongs to an extension, make sure it is properly installed.\n" .. + "If the extension was recently installed, try removing the /tmp/luci-indexcache file.") + end + end +end + +--- Generate the dispatching index using the best possible strategy. +function createindex() + local path = luci.util.libpath() .. "/controller/" + local suff = { ".lua", ".lua.gz" } + + if luci.util.copcall(require, "luci.fastindex") then + createindex_fastindex(path, suff) + else + createindex_plain(path, suff) + end +end + +--- Generate the dispatching index using the fastindex C-indexer. +-- @param path Controller base directory +-- @param suffixes Controller file suffixes +function createindex_fastindex(path, suffixes) + index = {} + + if not fi then + fi = luci.fastindex.new("index") + for _, suffix in ipairs(suffixes) do + fi.add(path .. "*" .. suffix) + fi.add(path .. "*/*" .. suffix) + end + end + fi.scan() + + for k, v in pairs(fi.indexes) do + index[v[2]] = v[1] + end +end + +--- Generate the dispatching index using the native file-cache based strategy. +-- @param path Controller base directory +-- @param suffixes Controller file suffixes +function createindex_plain(path, suffixes) + local controllers = { } + for _, suffix in ipairs(suffixes) do + nixio.util.consume((fs.glob(path .. "*" .. suffix)), controllers) + nixio.util.consume((fs.glob(path .. "*/*" .. suffix)), controllers) + end + + if indexcache then + local cachedate = fs.stat(indexcache, "mtime") + if cachedate then + local realdate = 0 + for _, obj in ipairs(controllers) do + local omtime = fs.stat(obj, "mtime") + realdate = (omtime and omtime > realdate) and omtime or realdate + end + + if cachedate > realdate then + assert( + sys.process.info("uid") == fs.stat(indexcache, "uid") + and fs.stat(indexcache, "modestr") == "rw-------", + "Fatal: Indexcache is not sane!" + ) + + index = loadfile(indexcache)() + return index + end + end + end + + index = {} + + for i,c in ipairs(controllers) do + local modname = "luci.controller." .. c:sub(#path+1, #c):gsub("/", ".") + for _, suffix in ipairs(suffixes) do + modname = modname:gsub(suffix.."$", "") + end + + local mod = require(modname) + assert(mod ~= true, + "Invalid controller file found\n" .. + "The file '" .. c .. "' contains an invalid module line.\n" .. + "Please verify whether the module name is set to '" .. modname .. + "' - It must correspond to the file path!") + + local idx = mod.index + assert(type(idx) == "function", + "Invalid controller file found\n" .. + "The file '" .. c .. "' contains no index() function.\n" .. + "Please make sure that the controller contains a valid " .. + "index function and verify the spelling!") + + index[modname] = idx + end + + if indexcache then + local f = nixio.open(indexcache, "w", 600) + f:writeall(util.get_bytecode(index)) + f:close() + end +end + +--- Create the dispatching tree from the index. +-- Build the index before if it does not exist yet. +function createtree() + if not index then + createindex() + end + + local ctx = context + local tree = {nodes={}, inreq=true} + local modi = {} + + ctx.treecache = setmetatable({}, {__mode="v"}) + ctx.tree = tree + ctx.modifiers = modi + + -- Load default translation + require "luci.i18n".loadc("base") + + local scope = setmetatable({}, {__index = luci.dispatcher}) + + for k, v in pairs(index) do + scope._NAME = k + setfenv(v, scope) + v() + end + + local function modisort(a,b) + return modi[a].order < modi[b].order + end + + for _, v in util.spairs(modi, modisort) do + scope._NAME = v.module + setfenv(v.func, scope) + v.func() + end + + return tree +end + +--- Register a tree modifier. +-- @param func Modifier function +-- @param order Modifier order value (optional) +function modifier(func, order) + context.modifiers[#context.modifiers+1] = { + func = func, + order = order or 0, + module + = getfenv(2)._NAME + } +end + +--- Clone a node of the dispatching tree to another position. +-- @param path Virtual path destination +-- @param clone Virtual path source +-- @param title Destination node title (optional) +-- @param order Destination node order value (optional) +-- @return Dispatching tree node +function assign(path, clone, title, order) + local obj = node(unpack(path)) + obj.nodes = nil + obj.module = nil + + obj.title = title + obj.order = order + + setmetatable(obj, {__index = _create_node(clone)}) + + return obj +end + +--- Create a new dispatching node and define common parameters. +-- @param path Virtual path +-- @param target Target function to call when dispatched. +-- @param title Destination node title +-- @param order Destination node order value (optional) +-- @return Dispatching tree node +function entry(path, target, title, order) + local c = node(unpack(path)) + + c.target = target + c.title = title + c.order = order + c.module = getfenv(2)._NAME + + return c +end + +--- Fetch or create a dispatching node without setting the target module or +-- enabling the node. +-- @param ... Virtual path +-- @return Dispatching tree node +function get(...) + return _create_node({...}) +end + +--- Fetch or create a new dispatching node. +-- @param ... Virtual path +-- @return Dispatching tree node +function node(...) + local c = _create_node({...}) + + c.module = getfenv(2)._NAME + c.auto = nil + + return c +end + +function _create_node(path) + if #path == 0 then + return context.tree + end + + local name = table.concat(path, ".") + local c = context.treecache[name] + + if not c then + local last = table.remove(path) + local parent = _create_node(path) + + c = {nodes={}, auto=true} + -- the node is "in request" if the request path matches + -- at least up to the length of the node path + if parent.inreq and context.path[#path+1] == last then + c.inreq = true + end + parent.nodes[last] = c + context.treecache[name] = c + end + return c +end + +-- Subdispatchers -- + +function _firstchild() + local path = { unpack(context.path) } + local name = table.concat(path, ".") + local node = context.treecache[name] + + local lowest + if node and node.nodes and next(node.nodes) then + local k, v + for k, v in pairs(node.nodes) do + if not lowest or + (v.order or 100) < (node.nodes[lowest].order or 100) + then + lowest = k + end + end + end + + assert(lowest ~= nil, + "The requested node contains no childs, unable to redispatch") + + path[#path+1] = lowest + dispatch(path) +end + +--- Alias the first (lowest order) page automatically +function firstchild() + return { type = "firstchild", target = _firstchild } +end + +--- Create a redirect to another dispatching node. +-- @param ... Virtual path destination +function alias(...) + local req = {...} + return function(...) + for _, r in ipairs({...}) do + req[#req+1] = r + end + + dispatch(req) + end +end + +--- Rewrite the first x path values of the request. +-- @param n Number of path values to replace +-- @param ... Virtual path to replace removed path values with +function rewrite(n, ...) + local req = {...} + return function(...) + local dispatched = util.clone(context.dispatched) + + for i=1,n do + table.remove(dispatched, 1) + end + + for i, r in ipairs(req) do + table.insert(dispatched, i, r) + end + + for _, r in ipairs({...}) do + dispatched[#dispatched+1] = r + end + + dispatch(dispatched) + end +end + + +local function _call(self, ...) + local func = getfenv()[self.name] + assert(func ~= nil, + 'Cannot resolve function "' .. self.name .. '". Is it misspelled or local?') + + assert(type(func) == "function", + 'The symbol "' .. self.name .. '" does not refer to a function but data ' .. + 'of type "' .. type(func) .. '".') + + if #self.argv > 0 then + return func(unpack(self.argv), ...) + else + return func(...) + end +end + +--- Create a function-call dispatching target. +-- @param name Target function of local controller +-- @param ... Additional parameters passed to the function +function call(name, ...) + return {type = "call", argv = {...}, name = name, target = _call} +end + + +local _template = function(self, ...) + require "luci.template".render(self.view) +end + +--- Create a template render dispatching target. +-- @param name Template to be rendered +function template(name) + return {type = "template", view = name, target = _template} +end + + +local function _cbi(self, ...) + local cbi = require "luci.cbi" + local tpl = require "luci.template" + local http = require "luci.http" + + local config = self.config or {} + local maps = cbi.load(self.model, ...) + + local state = nil + + for i, res in ipairs(maps) do + res.flow = config + local cstate = res:parse() + if cstate and (not state or cstate < state) then + state = cstate + end + end + + local function _resolve_path(path) + return type(path) == "table" and build_url(unpack(path)) or path + end + + if config.on_valid_to and state and state > 0 and state < 2 then + http.redirect(_resolve_path(config.on_valid_to)) + return + end + + if config.on_changed_to and state and state > 1 then + http.redirect(_resolve_path(config.on_changed_to)) + return + end + + if config.on_success_to and state and state > 0 then + http.redirect(_resolve_path(config.on_success_to)) + return + end + + if config.state_handler then + if not config.state_handler(state, maps) then + return + end + end + + http.header("X-CBI-State", state or 0) + + if not config.noheader then + tpl.render("cbi/header", {state = state}) + end + + local redirect + local messages + local applymap = false + local pageaction = true + local parsechain = { } + + for i, res in ipairs(maps) do + if res.apply_needed and res.parsechain then + local c + for _, c in ipairs(res.parsechain) do + parsechain[#parsechain+1] = c + end + applymap = true + end + + if res.redirect then + redirect = redirect or res.redirect + end + + if res.pageaction == false then + pageaction = false + end + + if res.message then + messages = messages or { } + messages[#messages+1] = res.message + end + end + + for i, res in ipairs(maps) do + res:render({ + firstmap = (i == 1), + applymap = applymap, + redirect = redirect, + messages = messages, + pageaction = pageaction, + parsechain = parsechain + }) + end + + if not config.nofooter then + tpl.render("cbi/footer", { + flow = config, + pageaction = pageaction, + redirect = redirect, + state = state, + autoapply = config.autoapply + }) + end +end + +--- Create a CBI model dispatching target. +-- @param model CBI model to be rendered +function cbi(model, config) + return {type = "cbi", config = config, model = model, target = _cbi} +end + + +local function _arcombine(self, ...) + local argv = {...} + local target = #argv > 0 and self.targets[2] or self.targets[1] + setfenv(target.target, self.env) + target:target(unpack(argv)) +end + +--- Create a combined dispatching target for non argv and argv requests. +-- @param trg1 Overview Target +-- @param trg2 Detail Target +function arcombine(trg1, trg2) + return {type = "arcombine", env = getfenv(), target = _arcombine, targets = {trg1, trg2}} +end + + +local function _form(self, ...) + local cbi = require "luci.cbi" + local tpl = require "luci.template" + local http = require "luci.http" + + local maps = luci.cbi.load(self.model, ...) + local state = nil + + for i, res in ipairs(maps) do + local cstate = res:parse() + if cstate and (not state or cstate < state) then + state = cstate + end + end + + http.header("X-CBI-State", state or 0) + tpl.render("header") + for i, res in ipairs(maps) do + res:render() + end + tpl.render("footer") +end + +--- Create a CBI form model dispatching target. +-- @param model CBI form model tpo be rendered +function form(model) + return {type = "cbi", model = model, target = _form} +end + +--- Access the luci.i18n translate() api. +-- @class function +-- @name translate +-- @param text Text to translate +translate = i18n.translate + +--- No-op function used to mark translation entries for menu labels. +-- This function does not actually translate the given argument but +-- is used by build/i18n-scan.pl to find translatable entries. +function _(text) + return text +end diff --git a/Me_Lua/r13/luci/model/network.lua b/Me_Lua/r13/luci/model/network.lua new file mode 100644 index 0000000..41b0e99 --- /dev/null +++ b/Me_Lua/r13/luci/model/network.lua @@ -0,0 +1,1632 @@ +--[[ +LuCI - Network model + +Copyright 2009-2010 Jo-Philipp Wich + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +]]-- + +local type, next, pairs, ipairs, loadfile, table + = type, next, pairs, ipairs, loadfile, table + +local tonumber, tostring, math = tonumber, tostring, math + +local require = require + +local bus = require "ubus" +local nxo = require "nixio" +local nfs = require "nixio.fs" +local ipc = require "luci.ip" +local sys = require "luci.sys" +local utl = require "luci.util" +local dsp = require "luci.dispatcher" +local uci = require "luci.model.uci" +local lng = require "luci.i18n" + +module "luci.model.network" + + +IFACE_PATTERNS_VIRTUAL = { } +IFACE_PATTERNS_IGNORE = { "^wmaster%d", "^wifi%d", "^hwsim%d", "^imq%d", "^ifb%d", "^mon%.wlan%d", "^sit%d", "^gre%d", "^lo$" } +IFACE_PATTERNS_WIRELESS = { "^wlan%d", "^wl%d", "^ath%d", "^%w+%.network%d" } + + +protocol = utl.class() + +local _protocols = { } + +local _interfaces, _bridge, _switch, _tunnel +local _ubus, _ubusnetcache, _ubusdevcache, _ubuswificache +local _uci_real, _uci_state + +function _filter(c, s, o, r) + local val = _uci_real:get(c, s, o) + if val then + local l = { } + if type(val) == "string" then + for val in val:gmatch("%S+") do + if val ~= r then + l[#l+1] = val + end + end + if #l > 0 then + _uci_real:set(c, s, o, table.concat(l, " ")) + else + _uci_real:delete(c, s, o) + end + elseif type(val) == "table" then + for _, val in ipairs(val) do + if val ~= r then + l[#l+1] = val + end + end + if #l > 0 then + _uci_real:set(c, s, o, l) + else + _uci_real:delete(c, s, o) + end + end + end +end + +function _append(c, s, o, a) + local val = _uci_real:get(c, s, o) or "" + if type(val) == "string" then + local l = { } + for val in val:gmatch("%S+") do + if val ~= a then + l[#l+1] = val + end + end + l[#l+1] = a + _uci_real:set(c, s, o, table.concat(l, " ")) + elseif type(val) == "table" then + local l = { } + for _, val in ipairs(val) do + if val ~= a then + l[#l+1] = val + end + end + l[#l+1] = a + _uci_real:set(c, s, o, l) + end +end + +function _stror(s1, s2) + if not s1 or #s1 == 0 then + return s2 and #s2 > 0 and s2 + else + return s1 + end +end + +function _get(c, s, o) + return _uci_real:get(c, s, o) +end + +function _set(c, s, o, v) + if v ~= nil then + if type(v) == "boolean" then v = v and "1" or "0" end + return _uci_real:set(c, s, o, v) + else + return _uci_real:delete(c, s, o) + end +end + +function _wifi_iface(x) + local _, p + for _, p in ipairs(IFACE_PATTERNS_WIRELESS) do + if x:match(p) then + return true + end + end + return false +end + +function _wifi_state(key, val, field) + if not next(_ubuswificache) then + _ubuswificache = _ubus:call("network.wireless", "status", {}) or {} + end + + local radio, radiostate + for radio, radiostate in pairs(_ubuswificache) do + local ifc, ifcstate + for ifc, ifcstate in pairs(radiostate.interfaces) do + if ifcstate[key] == val then + return ifcstate[field] + end + end + end +end + +function _wifi_lookup(ifn) + -- got a radio#.network# pseudo iface, locate the corresponding section + local radio, ifnidx = ifn:match("^(%w+)%.network(%d+)$") + if radio and ifnidx then + local sid = nil + local num = 0 + + ifnidx = tonumber(ifnidx) + _uci_real:foreach("wireless", "wifi-iface", + function(s) + if s.device == radio then + num = num + 1 + if num == ifnidx then + sid = s['.name'] + return false + end + end + end) + + return sid + + -- looks like wifi, try to locate the section via state vars + elseif _wifi_iface(ifn) then + local sid = _wifi_state("ifname", ifn, "section") + if not sid then + _uci_state:foreach("wireless", "wifi-iface", + function(s) + if s.ifname == ifn then + sid = s['.name'] + return false + end + end) + end + + return sid + end +end + +function _iface_virtual(x) + local _, p + for _, p in ipairs(IFACE_PATTERNS_VIRTUAL) do + if x:match(p) then + return true + end + end + return false +end + +function _iface_ignore(x) + local _, p + for _, p in ipairs(IFACE_PATTERNS_IGNORE) do + if x:match(p) then + return true + end + end + return _iface_virtual(x) +end + + +function init(cursor) + _uci_real = cursor or _uci_real or uci.cursor() + _uci_state = _uci_real:substate() + + _interfaces = { } + _bridge = { } + _switch = { } + _tunnel = { } + + _ubus = bus.connect() + _ubusnetcache = { } + _ubusdevcache = { } + _ubuswificache = { } + + -- read interface information + local n, i + for n, i in ipairs(nxo.getifaddrs()) do + local name = i.name:match("[^:]+") + local prnt = name:match("^([^%.]+)%.") + + if _iface_virtual(name) then + _tunnel[name] = true + end + + if _tunnel[name] or not _iface_ignore(name) then + _interfaces[name] = _interfaces[name] or { + idx = i.ifindex or n, + name = name, + rawname = i.name, + flags = { }, + ipaddrs = { }, + ip6addrs = { } + } + + if prnt then + _switch[name] = true + _switch[prnt] = true + end + + if i.family == "packet" then + _interfaces[name].flags = i.flags + _interfaces[name].stats = i.data + _interfaces[name].macaddr = i.addr + elseif i.family == "inet" then + _interfaces[name].ipaddrs[#_interfaces[name].ipaddrs+1] = ipc.IPv4(i.addr, i.netmask) + elseif i.family == "inet6" then + _interfaces[name].ip6addrs[#_interfaces[name].ip6addrs+1] = ipc.IPv6(i.addr, i.netmask) + end + end + end + + -- read bridge informaton + local b, l + for l in utl.execi("brctl show") do + if not l:match("STP") then + local r = utl.split(l, "%s+", nil, true) + if #r == 4 then + b = { + name = r[1], + id = r[2], + stp = r[3] == "yes", + ifnames = { _interfaces[r[4]] } + } + if b.ifnames[1] then + b.ifnames[1].bridge = b + end + _bridge[r[1]] = b + elseif b then + b.ifnames[#b.ifnames+1] = _interfaces[r[2]] + b.ifnames[#b.ifnames].bridge = b + end + end + end + + return _M +end + +function save(self, ...) + _uci_real:save(...) + _uci_real:load(...) +end + +function commit(self, ...) + _uci_real:commit(...) + _uci_real:load(...) +end + +function ifnameof(self, x) + if utl.instanceof(x, interface) then + return x:name() + elseif utl.instanceof(x, protocol) then + return x:ifname() + elseif type(x) == "string" then + return x:match("^[^:]+") + end +end + +function get_protocol(self, protoname, netname) + local v = _protocols[protoname] + if v then + return v(netname or "__dummy__") + end +end + +function get_protocols(self) + local p = { } + local _, v + for _, v in ipairs(_protocols) do + p[#p+1] = v("__dummy__") + end + return p +end + +function register_protocol(self, protoname) + local proto = utl.class(protocol) + + function proto.__init__(self, name) + self.sid = name + end + + function proto.proto(self) + return protoname + end + + _protocols[#_protocols+1] = proto + _protocols[protoname] = proto + + return proto +end + +function register_pattern_virtual(self, pat) + IFACE_PATTERNS_VIRTUAL[#IFACE_PATTERNS_VIRTUAL+1] = pat +end + + +function has_ipv6(self) + return nfs.access("/proc/net/ipv6_route") +end + +function add_network(self, n, options) + local oldnet = self:get_network(n) + if n and #n > 0 and n:match("^[a-zA-Z0-9_]+$") and not oldnet then + if _uci_real:section("network", "interface", n, options) then + return network(n) + end + elseif oldnet and oldnet:is_empty() then + if options then + local k, v + for k, v in pairs(options) do + oldnet:set(k, v) + end + end + return oldnet + end +end + +function get_network(self, n) + if n and _uci_real:get("network", n) == "interface" then + return network(n) + end +end + +function get_networks(self) + local nets = { } + local nls = { } + + _uci_real:foreach("network", "interface", + function(s) + nls[s['.name']] = network(s['.name']) + end) + + local n + for n in utl.kspairs(nls) do + nets[#nets+1] = nls[n] + end + + return nets +end + +function del_network(self, n) + local r = _uci_real:delete("network", n) + if r then + _uci_real:delete_all("network", "alias", + function(s) return (s.interface == n) end) + + _uci_real:delete_all("network", "route", + function(s) return (s.interface == n) end) + + _uci_real:delete_all("network", "route6", + function(s) return (s.interface == n) end) + + _uci_real:foreach("wireless", "wifi-iface", + function(s) + local net + local rest = { } + for net in utl.imatch(s.network) do + if net ~= n then + rest[#rest+1] = net + end + end + if #rest > 0 then + _uci_real:set("wireless", s['.name'], "network", + table.concat(rest, " ")) + else + _uci_real:delete("wireless", s['.name'], "network") + end + end) + end + return r +end + +function rename_network(self, old, new) + local r + if new and #new > 0 and new:match("^[a-zA-Z0-9_]+$") and not self:get_network(new) then + r = _uci_real:section("network", "interface", new, _uci_real:get_all("network", old)) + + if r then + _uci_real:foreach("network", "alias", + function(s) + if s.interface == old then + _uci_real:set("network", s['.name'], "interface", new) + end + end) + + _uci_real:foreach("network", "route", + function(s) + if s.interface == old then + _uci_real:set("network", s['.name'], "interface", new) + end + end) + + _uci_real:foreach("network", "route6", + function(s) + if s.interface == old then + _uci_real:set("network", s['.name'], "interface", new) + end + end) + + _uci_real:foreach("wireless", "wifi-iface", + function(s) + local net + local list = { } + for net in utl.imatch(s.network) do + if net == old then + list[#list+1] = new + else + list[#list+1] = net + end + end + if #list > 0 then + _uci_real:set("wireless", s['.name'], "network", + table.concat(list, " ")) + end + end) + + _uci_real:delete("network", old) + end + end + return r or false +end + +function get_interface(self, i) + if _interfaces[i] or _wifi_iface(i) then + return interface(i) + else + local ifc + local num = { } + _uci_real:foreach("wireless", "wifi-iface", + function(s) + if s.device then + num[s.device] = num[s.device] and num[s.device] + 1 or 1 + if s['.name'] == i then + ifc = interface( + "%s.network%d" %{s.device, num[s.device] }) + return false + end + end + end) + return ifc + end +end + +function get_interfaces(self) + local iface + local ifaces = { } + local seen = { } + local nfs = { } + local baseof = { } + + -- find normal interfaces + _uci_real:foreach("network", "interface", + function(s) + for iface in utl.imatch(s.ifname) do + if not _iface_ignore(iface) and not _wifi_iface(iface) then + seen[iface] = true + nfs[iface] = interface(iface) + end + end + end) + + for iface in utl.kspairs(_interfaces) do + if not (seen[iface] or _iface_ignore(iface) or _wifi_iface(iface)) then + nfs[iface] = interface(iface) + end + end + + -- find vlan interfaces + _uci_real:foreach("network", "switch_vlan", + function(s) + if not s.device then + return + end + + local base = baseof[s.device] + if not base then + if not s.device:match("^eth%d") then + local l + for l in utl.execi("swconfig dev %q help 2>/dev/null" % s.device) do + if not base then + base = l:match("^%w+: (%w+)") + end + end + if not base or not base:match("^eth%d") then + base = "eth0" + end + else + base = s.device + end + baseof[s.device] = base + end + + local vid = tonumber(s.vid or s.vlan) + if vid ~= nil and vid >= 0 and vid <= 4095 then + local iface = "%s.%d" %{ base, vid } + if not seen[iface] then + seen[iface] = true + nfs[iface] = interface(iface) + end + end + end) + + for iface in utl.kspairs(nfs) do + ifaces[#ifaces+1] = nfs[iface] + end + + -- find wifi interfaces + local num = { } + local wfs = { } + _uci_real:foreach("wireless", "wifi-iface", + function(s) + if s.device then + num[s.device] = num[s.device] and num[s.device] + 1 or 1 + local i = "%s.network%d" %{ s.device, num[s.device] } + wfs[i] = interface(i) + end + end) + + for iface in utl.kspairs(wfs) do + ifaces[#ifaces+1] = wfs[iface] + end + + return ifaces +end + +function ignore_interface(self, x) + return _iface_ignore(x) +end + +function get_wifidev(self, dev) + if _uci_real:get("wireless", dev) == "wifi-device" then + return wifidev(dev) + end +end + +function get_wifidevs(self) + local devs = { } + local wfd = { } + + _uci_real:foreach("wireless", "wifi-device", + function(s) wfd[#wfd+1] = s['.name'] end) + + local dev + for _, dev in utl.vspairs(wfd) do + devs[#devs+1] = wifidev(dev) + end + + return devs +end + +function get_wifinet(self, net) + local wnet = _wifi_lookup(net) + if wnet then + return wifinet(wnet) + end +end + +function add_wifinet(self, net, options) + if type(options) == "table" and options.device and + _uci_real:get("wireless", options.device) == "wifi-device" + then + local wnet = _uci_real:section("wireless", "wifi-iface", nil, options) + return wifinet(wnet) + end +end + +function del_wifinet(self, net) + local wnet = _wifi_lookup(net) + if wnet then + _uci_real:delete("wireless", wnet) + return true + end + return false +end + +function get_status_by_route(self, addr, mask) + local _, object + for _, object in ipairs(_ubus:objects()) do + local net = object:match("^network%.interface%.(.+)") + if net then + local s = _ubus:call(object, "status", {}) + if s and s.route then + local rt + for _, rt in ipairs(s.route) do + if not rt.table and rt.target == addr and rt.mask == mask then + return net, s + end + end + end + end + end +end + +function get_status_by_address(self, addr) + local _, object + for _, object in ipairs(_ubus:objects()) do + local net = object:match("^network%.interface%.(.+)") + if net then + local s = _ubus:call(object, "status", {}) + if s and s['ipv4-address'] then + local a + for _, a in ipairs(s['ipv4-address']) do + if a.address == addr then + return net, s + end + end + end + if s and s['ipv6-address'] then + local a + for _, a in ipairs(s['ipv6-address']) do + if a.address == addr then + return net, s + end + end + end + end + end +end + +function get_wannet(self) + local net = self:get_status_by_route("0.0.0.0", 0) + return net and network(net) +end + +function get_wandev(self) + local _, stat = self:get_status_by_route("0.0.0.0", 0) + return stat and interface(stat.l3_device or stat.device) +end + +function get_wan6net(self) + local net = self:get_status_by_route("::", 0) + return net and network(net) +end + +function get_wan6dev(self) + local _, stat = self:get_status_by_route("::", 0) + return stat and interface(stat.l3_device or stat.device) +end + + +function network(name, proto) + if name then + local p = proto or _uci_real:get("network", name, "proto") + local c = p and _protocols[p] or protocol + return c(name) + end +end + +function protocol.__init__(self, name) + self.sid = name +end + +function protocol._get(self, opt) + local v = _uci_real:get("network", self.sid, opt) + if type(v) == "table" then + return table.concat(v, " ") + end + return v or "" +end + +function protocol._ubus(self, field) + if not _ubusnetcache[self.sid] then + _ubusnetcache[self.sid] = _ubus:call("network.interface.%s" % self.sid, + "status", { }) + end + if _ubusnetcache[self.sid] and field then + return _ubusnetcache[self.sid][field] + end + return _ubusnetcache[self.sid] +end + +function protocol.get(self, opt) + return _get("network", self.sid, opt) +end + +function protocol.set(self, opt, val) + return _set("network", self.sid, opt, val) +end + +function protocol.ifname(self) + local ifname + if self:is_floating() then + ifname = self:_ubus("l3_device") + else + ifname = self:_ubus("device") + end + if not ifname then + local num = { } + _uci_real:foreach("wireless", "wifi-iface", + function(s) + if s.device then + num[s.device] = num[s.device] + and num[s.device] + 1 or 1 + + local net + for net in utl.imatch(s.network) do + if net == self.sid then + ifname = "%s.network%d" %{ s.device, num[s.device] } + return false + end + end + end + end) + end + return ifname +end + +function protocol.proto(self) + return "none" +end + +function protocol.get_i18n(self) + local p = self:proto() + if p == "none" then + return lng.translate("Unmanaged") + elseif p == "static" then + return lng.translate("Static address") + elseif p == "dhcp" then + return lng.translate("DHCP client") + else + return lng.translate("Unknown") + end +end + +function protocol.type(self) + return self:_get("type") +end + +function protocol.name(self) + return self.sid +end + +function protocol.uptime(self) + return self:_ubus("uptime") or 0 +end + +function protocol.expires(self) + local a = tonumber(_uci_state:get("network", self.sid, "lease_acquired")) + local l = tonumber(_uci_state:get("network", self.sid, "lease_lifetime")) + if a and l then + l = l - (nxo.sysinfo().uptime - a) + return l > 0 and l or 0 + end + return -1 +end + +function protocol.metric(self) + return tonumber(_uci_state:get("network", self.sid, "metric")) or 0 +end + +function protocol.ipaddr(self) + local addrs = self:_ubus("ipv4-address") + return addrs and #addrs > 0 and addrs[1].address +end + +function protocol.netmask(self) + local addrs = self:_ubus("ipv4-address") + return addrs and #addrs > 0 and + ipc.IPv4("0.0.0.0/%d" % addrs[1].mask):mask():string() +end + +function protocol.gwaddr(self) + local _, route + for _, route in ipairs(self:_ubus("route") or { }) do + if route.target == "0.0.0.0" and route.mask == 0 then + return route.nexthop + end + end +end + +function protocol.dnsaddrs(self) + local dns = { } + local _, addr + for _, addr in ipairs(self:_ubus("dns-server") or { }) do + if not addr:match(":") then + dns[#dns+1] = addr + end + end + return dns +end + +function protocol.ip6addr(self) + local addrs = self:_ubus("ipv6-address") + if addrs and #addrs > 0 then + return "%s/%d" %{ addrs[1].address, addrs[1].mask } + else + addrs = self:_ubus("ipv6-prefix-assignment") + if addrs and #addrs > 0 then + return "%s/%d" %{ addrs[1].address, addrs[1].mask } + end + end +end + +function protocol.gw6addr(self) + local _, route + for _, route in ipairs(self:_ubus("route") or { }) do + if route.target == "::" and route.mask == 0 then + return ipc.IPv6(route.nexthop):string() + end + end +end + +function protocol.dns6addrs(self) + local dns = { } + local _, addr + for _, addr in ipairs(self:_ubus("dns-server") or { }) do + if addr:match(":") then + dns[#dns+1] = addr + end + end + return dns +end + +function protocol.is_bridge(self) + return (not self:is_virtual() and self:type() == "bridge") +end + +function protocol.opkg_package(self) + return nil +end + +function protocol.is_installed(self) + return true +end + +function protocol.is_virtual(self) + return false +end + +function protocol.is_floating(self) + return false +end + +function protocol.is_empty(self) + if self:is_floating() then + return false + else + local rv = true + + if (self:_get("ifname") or ""):match("%S+") then + rv = false + end + + _uci_real:foreach("wireless", "wifi-iface", + function(s) + local n + for n in utl.imatch(s.network) do + if n == self.sid then + rv = false + return false + end + end + end) + + return rv + end +end + +function protocol.add_interface(self, ifname) + ifname = _M:ifnameof(ifname) + if ifname and not self:is_floating() then + -- if its a wifi interface, change its network option + local wif = _wifi_lookup(ifname) + if wif then + _append("wireless", wif, "network", self.sid) + + -- add iface to our iface list + else + _append("network", self.sid, "ifname", ifname) + end + end +end + +function protocol.del_interface(self, ifname) + ifname = _M:ifnameof(ifname) + if ifname and not self:is_floating() then + -- if its a wireless interface, clear its network option + local wif = _wifi_lookup(ifname) + if wif then _filter("wireless", wif, "network", self.sid) end + + -- remove the interface + _filter("network", self.sid, "ifname", ifname) + end +end + +function protocol.get_interface(self) + if self:is_virtual() then + _tunnel[self:proto() .. "-" .. self.sid] = true + return interface(self:proto() .. "-" .. self.sid, self) + elseif self:is_bridge() then + _bridge["br-" .. self.sid] = true + return interface("br-" .. self.sid, self) + else + local ifn = nil + local num = { } + for ifn in utl.imatch(_uci_real:get("network", self.sid, "ifname")) do + ifn = ifn:match("^[^:/]+") + return ifn and interface(ifn, self) + end + ifn = nil + _uci_real:foreach("wireless", "wifi-iface", + function(s) + if s.device then + num[s.device] = num[s.device] and num[s.device] + 1 or 1 + + local net + for net in utl.imatch(s.network) do + if net == self.sid then + ifn = "%s.network%d" %{ s.device, num[s.device] } + return false + end + end + end + end) + return ifn and interface(ifn, self) + end +end + +function protocol.get_interfaces(self) + if self:is_bridge() or (self:is_virtual() and not self:is_floating()) then + local ifaces = { } + + local ifn + local nfs = { } + for ifn in utl.imatch(self:get("ifname")) do + ifn = ifn:match("^[^:/]+") + nfs[ifn] = interface(ifn, self) + end + + for ifn in utl.kspairs(nfs) do + ifaces[#ifaces+1] = nfs[ifn] + end + + local num = { } + local wfs = { } + _uci_real:foreach("wireless", "wifi-iface", + function(s) + if s.device then + num[s.device] = num[s.device] and num[s.device] + 1 or 1 + + local net + for net in utl.imatch(s.network) do + if net == self.sid then + ifn = "%s.network%d" %{ s.device, num[s.device] } + wfs[ifn] = interface(ifn, self) + end + end + end + end) + + for ifn in utl.kspairs(wfs) do + ifaces[#ifaces+1] = wfs[ifn] + end + + return ifaces + end +end + +function protocol.contains_interface(self, ifname) + ifname = _M:ifnameof(ifname) + if not ifname then + return false + elseif self:is_virtual() and self:proto() .. "-" .. self.sid == ifname then + return true + elseif self:is_bridge() and "br-" .. self.sid == ifname then + return true + else + local ifn + for ifn in utl.imatch(self:get("ifname")) do + ifn = ifn:match("[^:]+") + if ifn == ifname then + return true + end + end + + local wif = _wifi_lookup(ifname) + if wif then + local n + for n in utl.imatch(_uci_real:get("wireless", wif, "network")) do + if n == self.sid then + return true + end + end + end + end + + return false +end + +function protocol.adminlink(self) + return dsp.build_url("admin", "network", "network", self.sid) +end + +function protocol.get_option_value(self,name) + return self:_get(name) +end + +function protocol.status(self) + local iface = uci.cursor_state():get_all("network", self.sid) + local proto = iface["proto"] + local ifname = iface["ifname"] + local device = iface["device"] + local up = tonumber(iface["up"]) + if proto == "pppoe" then + if device == nil then + return "down" + end + if up == nil then + return "connection" + end + if up == 1 then + return "up" + end + elseif proto == "3g" then + if device ~= ifname and up==nil then + return "down" + end + if device == ifname and up == nil then + return "connection" + end + if up == 1 then + return "up" + end + elseif proto == "static" then + if up == nil then + return "down" + end + if up == 1 then + return "up" + end + elseif proto == "dhcp" then + if up == nil then + return "down" + end + if up == 1 then + return "up" + end + end + return "unkown" +end + +interface = utl.class() + +function interface.__init__(self, ifname, network) + local wif = _wifi_lookup(ifname) + if wif then + self.wif = wifinet(wif) + self.ifname = _wifi_state("section", wif, "ifname") + end + + self.ifname = self.ifname or ifname + self.dev = _interfaces[self.ifname] + self.network = network +end + +function interface._ubus(self, field) + if not _ubusdevcache[self.ifname] then + _ubusdevcache[self.ifname] = _ubus:call("network.device", "status", + { name = self.ifname }) + end + if _ubusdevcache[self.ifname] and field then + return _ubusdevcache[self.ifname][field] + end + return _ubusdevcache[self.ifname] +end + +function interface.name(self) + return self.wif and self.wif:ifname() or self.ifname +end + +function interface.mac(self) + return (self:_ubus("macaddr") or "00:00:00:00:00:00"):upper() +end + +function interface.ipaddrs(self) + return self.dev and self.dev.ipaddrs or { } +end + +function interface.ip6addrs(self) + return self.dev and self.dev.ip6addrs or { } +end + +function interface.type(self) + if self.wif or _wifi_iface(self.ifname) then + return "wifi" + elseif _bridge[self.ifname] then + return "bridge" + elseif _tunnel[self.ifname] then + return "tunnel" + elseif self.ifname:match("%.") then + return "vlan" + elseif _switch[self.ifname] then + return "switch" + else + return "ethernet" + end +end + +function interface.shortname(self) + if self.wif then + return "%s %q" %{ + self.wif:active_mode(), + self.wif:active_ssid() or self.wif:active_bssid() + } + else + return self.ifname + end +end + +function interface.get_i18n(self) + if self.wif then + return "%s: %s %q" %{ + lng.translate("Wireless Network"), + self.wif:active_mode(), + self.wif:active_ssid() or self.wif:active_bssid() + } + else + return "%s: %q" %{ self:get_type_i18n(), self:name() } + end +end + +function interface.get_type_i18n(self) + local x = self:type() + if x == "wifi" then + return lng.translate("Wireless Adapter") + elseif x == "bridge" then + return lng.translate("Bridge") + elseif x == "switch" then + return lng.translate("Ethernet Switch") + elseif x == "vlan" then + return lng.translate("VLAN Interface") + elseif x == "tunnel" then + return lng.translate("Tunnel Interface") + else + return lng.translate("Ethernet Adapter") + end +end + +function interface.adminlink(self) + if self.wif then + return self.wif:adminlink() + end +end + +function interface.ports(self) + local members = self:_ubus("bridge-members") + if members then + local _, iface + local ifaces = { } + for _, iface in ipairs(members) do + ifaces[#ifaces+1] = interface(iface) + end + end +end + +function interface.bridge_id(self) + if self.br then + return self.br.id + else + return nil + end +end + +function interface.bridge_stp(self) + if self.br then + return self.br.stp + else + return false + end +end + +function interface.is_up(self) + return self:_ubus("up") or false +end + +function interface.is_bridge(self) + return (self:type() == "bridge") +end + +function interface.is_bridgeport(self) + return self.dev and self.dev.bridge and true or false +end + +function interface.tx_bytes(self) + local stat = self:_ubus("statistics") + return stat and stat.tx_bytes or 0 +end + +function interface.rx_bytes(self) + local stat = self:_ubus("statistics") + return stat and stat.rx_bytes or 0 +end + +function interface.tx_packets(self) + local stat = self:_ubus("statistics") + return stat and stat.tx_packets or 0 +end + +function interface.rx_packets(self) + local stat = self:_ubus("statistics") + return stat and stat.rx_packets or 0 +end + +function interface.get_network(self) + return self:get_networks()[1] +end + +function interface.get_networks(self) + if not self.networks then + local nets = { } + local _, net + for _, net in ipairs(_M:get_networks()) do + if net:contains_interface(self.ifname) or + net:ifname() == self.ifname + then + nets[#nets+1] = net + end + end + table.sort(nets, function(a, b) return a.sid < b.sid end) + self.networks = nets + return nets + else + return self.networks + end +end + +function interface.get_wifinet(self) + return self.wif +end + + +wifidev = utl.class() + +function wifidev.__init__(self, dev) + self.sid = dev + self.iwinfo = dev and sys.wifi.getiwinfo(dev) or { } +end + +function wifidev.get(self, opt) + return _get("wireless", self.sid, opt) +end + +function wifidev.set(self, opt, val) + return _set("wireless", self.sid, opt, val) +end + +function wifidev.name(self) + return self.sid +end + +function wifidev.hwmodes(self) + local l = self.iwinfo.hwmodelist + if l and next(l) then + return l + else + return { b = true, g = true } + end +end + +function wifidev.get_i18n(self) + local t = "Generic" + if self.iwinfo.type == "wl" then + t = "Broadcom" + elseif self.iwinfo.type == "madwifi" then + t = "Atheros" + end + + local m = "" + local l = self:hwmodes() + if l.a then m = m .. "a" end + if l.b then m = m .. "b" end + if l.g then m = m .. "g" end + if l.n then m = m .. "n" end + if l.ac then m = "ac" end + + return "%s 802.11%s Wireless Controller (%s)" %{ t, m, self:name() } +end + +function wifidev.is_up(self) + if _ubuswificache[self.sid] then + return (_ubuswificache[self.sid].up == true) + end + + local up = false + _uci_state:foreach("wireless", "wifi-iface", + function(s) + if s.device == self.sid then + if s.up == "1" then + up = true + return false + end + end + end) + + return up +end + +function wifidev.get_wifinet(self, net) + if _uci_real:get("wireless", net) == "wifi-iface" then + return wifinet(net) + else + local wnet = _wifi_lookup(net) + if wnet then + return wifinet(wnet) + end + end +end + +function wifidev.get_wifinets(self) + local nets = { } + + _uci_real:foreach("wireless", "wifi-iface", + function(s) + if s.device == self.sid then + nets[#nets+1] = wifinet(s['.name']) + end + end) + + return nets +end + +function wifidev.add_wifinet(self, options) + options = options or { } + options.device = self.sid + + local wnet = _uci_real:section("wireless", "wifi-iface", nil, options) + if wnet then + return wifinet(wnet, options) + end +end + +function wifidev.del_wifinet(self, net) + if utl.instanceof(net, wifinet) then + net = net.sid + elseif _uci_real:get("wireless", net) ~= "wifi-iface" then + net = _wifi_lookup(net) + end + + if net and _uci_real:get("wireless", net, "device") == self.sid then + _uci_real:delete("wireless", net) + return true + end + + return false +end + + +wifinet = utl.class() + +function wifinet.__init__(self, net, data) + self.sid = net + + local num = { } + local netid + _uci_real:foreach("wireless", "wifi-iface", + function(s) + if s.device then + num[s.device] = num[s.device] and num[s.device] + 1 or 1 + if s['.name'] == self.sid then + netid = "%s.network%d" %{ s.device, num[s.device] } + return false + end + end + end) + + local dev = _wifi_state("section", self.sid, "ifname") or netid + + self.netid = netid + self.wdev = dev + self.iwinfo = dev and sys.wifi.getiwinfo(dev) or { } + self.iwdata = data or _uci_state:get_all("wireless", self.sid) or + _uci_real:get_all("wireless", self.sid) or { } +end + +function wifinet.get(self, opt) + return _get("wireless", self.sid, opt) +end + +function wifinet.set(self, opt, val) + return _set("wireless", self.sid, opt, val) +end + +function wifinet.mode(self) + return _uci_state:get("wireless", self.sid, "mode") or "ap" +end + +function wifinet.ssid(self) + return _uci_state:get("wireless", self.sid, "ssid") +end + +function wifinet.bssid(self) + return _uci_state:get("wireless", self.sid, "bssid") +end + +function wifinet.network(self) + return _uci_state:get("wifinet", self.sid, "network") +end + +function wifinet.id(self) + return self.netid +end + +function wifinet.name(self) + return self.sid +end + +function wifinet.ifname(self) + local ifname = self.iwinfo.ifname + if not ifname or ifname:match("^wifi%d") or ifname:match("^radio%d") then + ifname = self.wdev + end + return ifname +end + +function wifinet.get_device(self) + if self.iwdata.device then + return wifidev(self.iwdata.device) + end +end + +function wifinet.is_up(self) + local ifc = self:get_interface() + return (ifc and ifc:is_up() or false) +end + +function wifinet.active_mode(self) + local m = _stror(self.iwinfo.mode, self.iwdata.mode) or "ap" + + if m == "ap" then m = "Master" + elseif m == "sta" then m = "Client" + elseif m == "adhoc" then m = "Ad-Hoc" + elseif m == "mesh" then m = "Mesh" + elseif m == "monitor" then m = "Monitor" + end + + return m +end + +function wifinet.active_mode_i18n(self) + return lng.translate(self:active_mode()) +end + +function wifinet.active_ssid(self) + return _stror(self.iwinfo.ssid, self.iwdata.ssid) +end + +function wifinet.active_bssid(self) + return _stror(self.iwinfo.bssid, self.iwdata.bssid) or "00:00:00:00:00:00" +end + +function wifinet.active_encryption(self) + local enc = self.iwinfo and self.iwinfo.encryption + return enc and enc.description or "-" +end + +function wifinet.assoclist(self) + return self.iwinfo.assoclist or { } +end + +function wifinet.frequency(self) + local freq = self.iwinfo.frequency + if freq and freq > 0 then + return "%.03f" % (freq / 1000) + end +end + +function wifinet.bitrate(self) + local rate = self.iwinfo.bitrate + if rate and rate > 0 then + return (rate / 1000) + end +end + +function wifinet.channel(self) + return self.iwinfo.channel or + tonumber(_uci_state:get("wireless", self.iwdata.device, "channel")) +end + +function wifinet.signal(self) + return self.iwinfo.signal or 0 +end + +function wifinet.noise(self) + return self.iwinfo.noise or 0 +end + +function wifinet.country(self) + return self.iwinfo.country or "00" +end + +function wifinet.txpower(self) + local pwr = (self.iwinfo.txpower or 0) + return pwr + self:txpower_offset() +end + +function wifinet.txpower_offset(self) + return self.iwinfo.txpower_offset or 0 +end + +function wifinet.signal_level(self, s, n) + if self:active_bssid() ~= "00:00:00:00:00:00" then + local signal = s or self:signal() + local noise = n or self:noise() + + if signal < 0 and noise < 0 then + local snr = -1 * (noise - signal) + return math.floor(snr / 5) + else + return 0 + end + else + return -1 + end +end + +function wifinet.signal_percent(self) + local qc = self.iwinfo.quality or 0 + local qm = self.iwinfo.quality_max or 0 + + if qc > 0 and qm > 0 then + return math.floor((100 / qm) * qc) + else + return 0 + end +end + +function wifinet.shortname(self) + return "%s %q" %{ + lng.translate(self:active_mode()), + self:active_ssid() or self:active_bssid() + } +end + +function wifinet.get_i18n(self) + return "%s: %s %q (%s)" %{ + lng.translate("Wireless Network"), + lng.translate(self:active_mode()), + self:active_ssid() or self:active_bssid(), + self:ifname() + } +end + +function wifinet.adminlink(self) + return dsp.build_url("admin", "network", "wireless", self.netid) +end + +function wifinet.get_network(self) + return self:get_networks()[1] +end + +function wifinet.get_networks(self) + local nets = { } + local net + for net in utl.imatch(tostring(self.iwdata.network)) do + if _uci_real:get("network", net) == "interface" then + nets[#nets+1] = network(net) + end + end + table.sort(nets, function(a, b) return a.sid < b.sid end) + return nets +end + +function wifinet.get_interface(self) + return interface(self:ifname()) +end + + +-- setup base protocols +_M:register_protocol("static") +_M:register_protocol("dhcp") +_M:register_protocol("none") + +-- load protocol extensions +local exts = nfs.dir(utl.libpath() .. "/model/network") +if exts then + local ext + for ext in exts do + if ext:match("%.lua$") then + require("luci.model.network." .. ext:gsub("%.lua$", "")) + end + end +end diff --git a/Me_Lua/r13/luci/sauth.lua b/Me_Lua/r13/luci/sauth.lua new file mode 100644 index 0000000..ec02be8 --- /dev/null +++ b/Me_Lua/r13/luci/sauth.lua @@ -0,0 +1,200 @@ +--[[ + +Session authentication +(c) 2008 Steven Barth + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +$Id$ + +]]-- + +--- LuCI session library. +module("luci.sauth", package.seeall) +require("luci.util") +require("luci.sys") +require("luci.config") +local nixio = require "nixio", require "nixio.util" +local fs = require "nixio.fs" + + +luci.config.sauth = luci.config.sauth or {} +sessionpath = luci.config.sauth.sessionpath +sessiontime = tonumber(luci.config.sauth.sessiontime) or 15 * 60 + +--- Prepare session storage by creating the session directory. +function prepare() + fs.mkdir(sessionpath, 700) + if not sane() then + error("Security Exception: Session path is not sane!") + end +end + +local function _read(id) + local blob = fs.readfile(sessionpath .. "/" .. id) + return blob +end + +local function _write(id, data) + local tempid = luci.sys.uniqueid(16) + local tempfile = sessionpath .. "/" .. tempid + local sessfile = sessionpath .. "/" .. id + local f = nixio.open(tempfile, "w", 600) + f:writeall(data) + f:close() + fs.rename(tempfile, sessfile) +end + +local function _checkid(id) + return not not (id and #id == 32 and id:match("^[a-fA-F0-9]+$")) +end + +--- Write session data to a session file. +-- @param id Session identifier +-- @param data Session data table +function write(id, data) + if not sane() then + prepare() + end + + if not _checkid(id) then + return + end + + if type(data) ~= "table" then + return + end + + data.atime = luci.sys.uptime() + + _write(id, luci.util.get_bytecode(data)) +end + +--- Read a session and return its content. +-- @param id Session identifier +-- @return Session data table or nil if the given id is not found +function read(id) + if not id or #id == 0 then + return nil + end + + if not _checkid(id) then + return nil + end + + if not sane(sessionpath .. "/" .. id) then + return nil + end + + local blob = _read(id) + local func = loadstring(blob) + setfenv(func, {}) + + local sess = func() + if type(sess) ~= "table" then + return nil + end + + if sess.atime and sess.atime + sessiontime < luci.sys.uptime() then + kill(id) + return nil + end + + -- refresh atime in session + write(id, sess) + + return sess +end + +--- Check whether Session environment is sane. +-- @return Boolean status +function sane(file) + return luci.sys.process.info("uid") + == fs.stat(file or sessionpath, "uid") + and fs.stat(file or sessionpath, "modestr") + == (file and "rw-------" or "rwx------") +end + +--- Kills a session +-- @param id Session identifier +function kill(id) + if not _checkid(id) then + else + fs.unlink(sessionpath .. "/" .. id) + end +end + +--- Remove all expired session data files +function reap() + if sane() then + local id + for id in nixio.fs.dir(sessionpath) do + if _checkid(id) then + -- reading the session will kill it if it is expired + read(id) + end + end + end +end + +--- Get available session data +function available() + if sane() then + local id + for id in nixio.fs.dir(sessionpath) do + if _checkid(id) then + -- reading the session will kill it if it is expired + local available = read(id) + if available then + return available + end + end + end + end + return nil +end + +function genAuthToken() + local id = luci.sys.uniqueid(16) + local token = luci.sys.uniqueid(16) + write(id, { + user="root", + token=token, + secret=luci.sys.uniqueid(16) + }) + return id, read(id) +end + +function noAuthGetToken() + local id + local data + local ret = {} + if sessionpath then + local ids = nixio.fs.dir(sessionpath) + require "MZLog".log(3, debug.getinfo(1).currentline) + if not ids then + genAuthToken() + end + for id in nixio.fs.dir(sessionpath) do + if id then + read(id) + end + end + for id in nixio.fs.dir(sessionpath) do + if id then + ret["sysauth"] = id + ret["data"] = read(id) + return ret + end + end + require "MZLog".log(3, debug.getinfo(1).currentline) + id, data = genAuthToken() + ret["sysauth"] = id + ret["data"] = read(id) + return ret + end +end diff --git a/Me_Lua/r13/meizu/arpmon.lua b/Me_Lua/r13/meizu/arpmon.lua new file mode 100644 index 0000000..ccecba8 --- /dev/null +++ b/Me_Lua/r13/meizu/arpmon.lua @@ -0,0 +1,103 @@ +module("meizu.arpmon", package.seeall) +--network functions + +local cjson = require "cjson" +local lfs = require "lfs" +local bfs = require "meizu.bfs" +local dbfs = require "meizu.dbfs" +local RC = require "meizu.r10config" +local sipfs = require "meizu.sipfs" +local nwfs = require "meizu.nwfs" + +local lue = require("luci.util").exec + +local bind_router = bfs.bind_router +local data_to_json = bfs.data_to_json +local exec_cmd_in_sh = bfs.exec_cmd_in_sh +local exec_reboot = bfs.exec_reboot +local get_device_SN = bfs.get_device_SN +local get_device_version = bfs.get_device_version +local get_https_data = bfs.get_https_data +local rts_get_access_token = bfs.rts_get_access_token +local set_passwd = bfs.set_passwd +local silent_upgrade = bfs.silent_upgrade +local strsplit = bfs.strsplit + +local delete_arp_all_mac = dbfs.delete_arp_all_mac +local get_dev_nick_name = dbfs.get_dev_nick_name +local init_arp_table = dbfs.init_arp_table +local insert_arp_macip = dbfs.insert_arp_macip +local fetch_all_arp = dbfs.fetch_all_arp +local update_arp = dbfs.update_arp + +local getAllWifiConnetDeviceDict = nwfs.getAllWifiConnetDeviceDict + +function new_device_notify() + + init_arp_table() + local ret = {} + local cmd = [[cat /proc/net/arp |grep br-lan|awk '{print $1","$4}']] + local ipmacs = {} + local devs = lue(cmd) + if devs ~= "" then + ipmacs = strsplit(devs, '\n') + end + ipmacs[#ipmacs] = nil + local new_devs = {} + local allarp = fetch_all_arp() + local wifiDeviceDict = getAllWifiConnetDeviceDict() + if nil == allarp then + new_devs = ipmacs + else + for k, v in pairs(ipmacs) do + local ipmac = strsplit(v, ',') + local ip = ipmac[1] + local mac = ipmac[2] + mac = string.upper(mac) + local isnew = true + local wf = 0 + for index, value in pairs(allarp) do + if mac == string.upper(value["mac"]) then + isnew = false + wf = value["wifi"] + break + end + end + if isnew == true then + table.insert(new_devs,v) + else + local wdd = wifiDeviceDict[mac] + if nil ~= wdd then + wf = wdd.wifiIndex + end + update_arp(mac, ip, wf) + end + end + end + for k, v in pairs(new_devs) do + local ipmac = strsplit(v, ',') + local ip = ipmac[1] + local mac = string.upper(ipmac[2]) + if ip ~= "" then + local wifi = 0 + local wdd = wifiDeviceDict[mac] + if nil ~= wdd then + wifi = wdd.wifiIndex + end + if "00:00:00:00:00:00" ~= mac then + insert_arp_macip(mac, ip, wifi) + end + local logtype = 1 + ret["mac_address"] = mac + local nickname = get_dev_nick_name(mac) + if nickname and nickname ~= "" then + ret["name"] = nickname + else + ret["name"] = ip + end + local res, code, headers, status = sipfs.upload_router_log(data_to_json(ret), logtype) + end + end + luci.http.write_json(ret) +end + diff --git a/Me_Lua/r13/meizu/bfs.lua b/Me_Lua/r13/meizu/bfs.lua new file mode 100644 index 0000000..0906acb --- /dev/null +++ b/Me_Lua/r13/meizu/bfs.lua @@ -0,0 +1,370 @@ +module("meizu.bfs", package.seeall) +--API base functions + +local cjson = require "cjson" +local dbfs = require "meizu.dbfs" + +function cal_str_md5(str) + local md5 = "" + local cmd = [[/bin/echo -n ']]..str + cmd = cmd..[['|/usr/bin/md5sum|/usr/bin/cut -d" " -f1]] + local fd = io.popen(cmd) + local ln = fd:read("*l") + if ln ~= nil then + md5 = ln + end + fd:close() + return md5 +end + +function strsplit(str, delim, maxNb) + local result = {} + if delim == nil then + delim = '\n' + end + if string.find(str, delim) == nil then + return { str } + end + if maxNb == nil or maxNb < 1 then + maxNb = 0 + end + local pat = "(.-)" .. delim .. "()" + local nb = 0 + local lastPos + for part, pos in string.gfind(str, pat) do + nb = nb + 1 + result[nb] = part + lastPos = pos + if nb == maxNb then break end + end + if nb ~= maxNb then + result[nb + 1] = string.sub(str, lastPos) + end + + return result +end + +function data_to_json(x) + local buf = "" + if x == nil then + return "" + elseif x == "" then + return '""' + elseif type(x) == "table" then + local k, v + if type(next(x)) == "number" then + buf = buf.."[ " + for k, v in ipairs(x) do + buf = buf..data_to_json(v) + if next(x, k) then + buf = buf..", " + end + end + buf = buf.." ]" + else + buf = buf.."{ " + for k, v in pairs(x) do + buf = buf..string.format("%q: " % k) + buf = buf..data_to_json(v) + if next(x, k) then + buf = buf..", " + end + end + buf = buf.." }" + end + elseif type(x) == "number" or type(x) == "boolean" then + if (x ~= x) then + buf = buf.."Number.NaN" + else + buf = buf..tostring(x) + end + else + buf = buf..string.format('"%s"' % tostring(x):gsub('[%z\1-\31]', function(c) return '\\u%04x' % c:byte(1) end)) + end + return buf +end + +function exec_cmd_in_sh(command) + local nio = require("nixio") + require "MZLog".log(3, command) + local pid = nio.fork() + if pid > 0 then + return + elseif pid == 0 then + nio.chdir("/") + local null = nio.open("/dev/null", "w+") + if null then + nio.dup(null, nio.stderr) + nio.dup(null, nio.stdout) + nio.dup(null, nio.stdin) + if null:fileno() > 2 then + null:close() + end + end + nio.exec("/bin/sh", "-c", command) + end +end + +--local get_https_data = function(url, data) return require("ssl.https").request(url, data) end +--return res, code, headers, status +function get_https_data(url, data) + if data ~= nil then + return require("ssl.https").request(url, data) + else + return require("ssl.https").request(url) + end +end + +function decodeURI(s) + local s = string.gsub(s, '%%(%x%x)', function(h) return string.char(tonumber(h, 16)) end) + return s +end + +function encodeURI(s) + local s = string.gsub(s, "([^%w%.%- ])", function(c) return string.format("%%%02X", string.byte(c)) end) + return string.gsub(s, " ", "+") +end + +function b64enc(data) + local b='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' + return ((data:gsub('.', function(x) + local r,b='',x:byte() + for i=8,1,-1 do r=r..(b%2^i-b%2^(i-1)>0 and '1' or '0') end + return r; + end)..'0000'):gsub('%d%d%d?%d?%d?%d?', function(x) + if (#x < 6) then return '' end + local c=0 + for i=1,6 do c=c+(x:sub(i,i)=='1' and 2^(6-i) or 0) end + return b:sub(c+1,c+1) + end)..({ '', '==', '=' })[#data%3+1]) +end + +function b64dec(data) + local b='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' + data = string.gsub(data, '[^'..b..'=]', '') + return (data:gsub('.', function(x) + if (x == '=') then return '' end + local r,f='',(b:find(x)-1) + for i=6,1,-1 do r=r..(f%2^i-f%2^(i-1)>0 and '1' or '0') end + return r; + end):gsub('%d%d%d?%d?%d?%d?%d?%d?', function(x) + if (#x ~= 8) then return '' end + local c=0 + for i=1,8 do c=c+(x:sub(i,i)=='1' and 2^(8-i) or 0) end + return string.char(c) + end)) +end + +function exec_reboot() + --luci.sys.reboot() + exec_cmd_in_sh("reboot") +end + +function factory_reset() + --exec_cmd_in_sh("killall dropbear uhttpd; sleep 1; mtd erase nvram; mtd -r erase rootfs_data") + local result = {} + result["result"] = true + luci.http.write_json(result) + exec_cmd_in_sh("killall dropbear uhttpd; sleep 1; /usr/bin/router_reset;/sbin/reboot -f") +end + +function set_passwd() + local result = {} + local p1 = luci.http.formvalue("pwd1") + local p2 = luci.http.formvalue("pwd2") + local stat = nil + if p1 ~= nil and luci.sys.user.checkpasswd("root", p1) then + if p2 ~= nil then + stat = luci.sys.user.setpasswd("root", p2) + result["result"] = true + luci.http.write_json(result) + end + end + result["result"] = false + luci.http.write_json(result) +end + +function rts_get_access_token() + local dbfs = require "meizu.dbfs" + dbfs.init_access_token_table() + local dbdata = dbfs.fetch_access_token() + local token + if #dbdata > 0 then + for _, data in ipairs(dbdata) do + local tokenFromdb = data.token + local expireTimeFromdb = data.expireTime + local nowTime = os.time() + if expireTimeFromdb < nowTime then + require "MZLog".log(3, "expireTime expire") + local httpsData = https_get_access_token() + token = httpsData.access_token + dbfs.update_access_token(tokenFromdb, token, httpsData.expires_in + os.time() - 10) + else + require "MZLog".log(3, "token from db") + token = tokenFromdb + end + end + else + local httpsData = https_get_access_token() + token = httpsData.access_token + require "MZLog".log(3, "token from https") + dbfs.add_access_token(token, httpsData.expires_in + os.time() - 10) + end + return token +end + +function https_get_access_token() + local url = "https://api.meizu.com/oauth/token?scope=router_trust&device=" + local suffix = "&password=&grant_type=device_only&client_id=gkzyJzq4RPoaov3BamqsJgg&client_secret=yh9bdKurxxotCjrEvJOiumk2mrzhcyej" + local SN = get_device_SN() + url = url..SN..suffix + local res, code, headers, status = get_https_data(url) + local data = cjson.decode(res) + return data +end + +function get_user_access_token() + local res, code, headers, status + local url = 'https://api.meizu.com/oauth/token?grant_type=password&client_id=gkzyJzq4RPoaov3BamqsJgg&client_secret=yh9bdKurxxotCjrEvJOiumk2mrzhcyej&username=appadmin@flyme.cn&password=appadmin111&scope=router_trust' + local res, code, headers, status = get_https_data(url) + local data = cjson.decode(res) + return data.access_token +end + +function init_bind_router_body(access_token) + local body = "access_token=" + local uat = luci.http.formvalue("uat") + body = body..access_token + body = body.."&user_access_token=" + body = body..uat + return body +end + +function bind_router() + local url = "https://router.meizu.com/oauth/router/bindRouter" + local access_token = rts_get_access_token() + local body = init_bind_router_body(access_token) + local https = require("ssl.https") + local res, code, headers, status = https.request(url, body) + if code == 401 then + delete_access_token() + access_token = rts_get_access_token() + body = init_bind_router_body(access_token) + res, code, headers, status = https.request(url, body) + end + luci.http.write(res) +end + +function unbind_router() + local url = 'https://router.meizu.com/oauth/user/unbind?access_token=' + local https = require("ssl.https") + local uat = luci.http.formvalue("uat") + url = url..uat + url = url..'&device='..get_device_SN() + local res, code, headers, status = https.request(url) + luci.http.write(res) +end + +function get_device_SN() + local sn = "R13WZLCZ7AC1001" + local fd = io.popen("nvram get sn") + if fd then + local ln = fd:read("*l") + if ln ~= nil then + sn = ln + end + fd:close() + end + return sn +end + +function get_device_version() + local device_version = "1.0.0" + --[[ + [local pcall, dofile = pcall, dofile + [if pcall(dofile, "/etc/openwrt_release") then + [ if DISTRIB_RELEASE ~= nil then + [ device_version = DISTRIB_RELEASE + [ end + [end + ]] + local lu = require("luci.util") + local cmd = [[cat /etc/openwrt_version|awk '{printf $1}']] + local v = lu.exec(cmd) + if v ~= nil then + device_version = v + end + return device_version +end + +function silent_upgrade() + local fd = nil + local image = "/tmp/firmware.img" + local touchcmd = "touch "..image + exec_cmd_in_sh(touchcmd) + local function image_supported() + return ( 0 == os.execute( + ". /lib/functions.sh; " .. + "include /lib/upgrade; " .. + "platform_check_image %q >/dev/null" + % image + )) + end + if luci.http.formvalue("act") == "update" then + luci.http.write("act == update") + end + if image_supported() then + luci.http.write("updating") + exec_cmd_in_sh("killall dropbear uhttpd; sleep 1; /sbin/sysupgrade -v %q" %{ image }) + luci.http.write("update finished!") + else + luci.http.write("image_supported check failed!") + end +end + +function table_merge(t1, t2) + if (type(t1) == "table") and (type(t2) == "table") then + for k, v in pairs(t2) do + if (type(v) == "table") and (type(t1[k] or false) == "table") then + table_merge(t1[k], t2[k]) + else + t1[k] = v + end + end + end + return t1 +end + +function sysinfo() + local LuciNetwork = require("luci.model.network").init() + local lanNetwork = LuciNetwork:get_network("lan") + local wanNetwork = LuciNetwork:get_network("wan") + local wanType = "dhcp" + if wanNetwork and lanNetwork then + local apc = lanNetwork:get_option_value("apclient") + if nil ~= apc and "" ~= apc then + wanType = "apclient" + else + wanType = wanNetwork:proto() + end + end + local lue = require("luci.util").exec + local ret = {} + ret["wanType"] = wanType + ret["romversion"] = get_device_version() + ret["SN"] = get_device_SN() + ret["deviceModel"] = "R13" + ret["routername"] = "mzrt"..get_device_SN() + local ssid1, ssid2 = require "meizu.nwfs".get_wifi_ssids() + ret["ssid1"] = ssid2 + ret["ssid2"] = ssid1 + local cmd = [[df /mnt|grep -q sda;echo -n $?]] + ret["diskstatus"] = lue(cmd) + cmd = [[echo -n $(ifconfig br-lan |grep HWaddr|sed 's/.*HWaddr //' | sed 's/\ .*//')]] + ret["brlanmac"] = lue(cmd) + cmd = [[echo -n $(ifconfig br-lan |grep 'inet addr'|awk -F':' '{print $2}' |awk '{printf $1}')]] + ret["brlanip"] = lue(cmd) + + return ret +end + diff --git a/Me_Lua/r13/meizu/build.sh b/Me_Lua/r13/meizu/build.sh new file mode 100644 index 0000000..e3908c8 --- /dev/null +++ b/Me_Lua/r13/meizu/build.sh @@ -0,0 +1,5 @@ +#!/bin/sh +/etc/init.d/uhttpd stop +rm -rf /tmp/luci-indexcache /tmp/luci-modulecache +rm -rf /tmp/luci-sessions +/etc/init.d/uhttpd start diff --git a/Me_Lua/r13/meizu/dbfs.lua b/Me_Lua/r13/meizu/dbfs.lua new file mode 100644 index 0000000..e2f51cd --- /dev/null +++ b/Me_Lua/r13/meizu/dbfs.lua @@ -0,0 +1,410 @@ +module("meizu.dbfs", package.seeall) + +local sqlite3 = require("lsqlite3") +local r13db = "/etc/r13db" + +function database_busy() + return true +end + +function updateDeviceNickname(mac, nickname) + local db = sqlite3.open(r13db) + local sqlStr = string.format("update maclist set devicename = '%s' where mac = '%s'", nickname, mac) + db:exec(sqlStr) + return db:close() +end + +function get_dev_nick_name(mac) + local db = sqlite3.open(r13db) + local sqlStr = string.format("select devicename, orgname from maclist where mac like '%s'", mac) + local nickname = "" + for row in db:rows(sqlStr) do + if row[1] ~= "" then + nickname = row[1] + else + if row[2] ~= "" then + nickname = row[2] + end + end + end + db:close() + return nickname +end + +function fetchDenyDeviceInfo(mac) + local db = sqlite3.open(r13db) + local sqlStr = string.format("select * from maclist where mac = '%s'", mac) + local result = {} + for row in db:rows(sqlStr) do + if row then + table.insert(result,{ + ["mac"] = row[1], + ["orgname"] = row[2], + ["devicename"] = row[3], + ["ip"] = row[4] + }) + end + end + db:close() + return result +end + +function change_maclist_table() + local db = sqlite3.open(r13db) + local sqlStr = string.format("ALTER TABLE maclist ADD COLUMN ip varchar(100)") + db:exec(sqlStr) + return db:close() +end + +function fetchAllDeviceInfo() + local db = sqlite3.open(r13db) + local sqlStr = string.format("select * from maclist") + local result = {} + for row in db:rows(sqlStr) do + if row then + table.insert(result,{ + ["mac"] = row[1], + ["orgname"] = row[2], + ["devicename"] = row[3] + }) + end + end + db:close() + return result +end + +function updateDeviceOrgname(mac, orgname) + local db = sqlite3.open(r13db) + local sqlStr = string.format("update maclist set orgname = '%s' where mac = '%s'", orgname, mac) + db:exec(sqlStr) + return db:close() +end + +function saveDeviceInfo(mac, orgname, devicename, deviceip) + local db = sqlite3.open(r13db) + local fetch = string.format("select * from maclist where mac = '%s'", mac) + local exist = false + for row in db:rows(fetch) do + if row then + exist = true + end + end + local sqlStr + if not exist then + sqlStr = string.format("insert into maclist values('%s','%s','%s', '%s')", mac, orgname, devicename, deviceip) + else + sqlStr = string.format("update maclist set mac = '%s', orgname = '%s', devicemame = '%s', ip = '%s' where mac = '%s'", mac, orgname, devicename, deviceip, mac) + end + db:exec(sqlStr) + return db:close() +end + +function init_arp_table() + local db = sqlite3.open(r13db) + local sqlStr = string.format("create table if not exists arp(mac varchar(18), ip varchar(16), wifi integer)") + db:exec(sqlStr) + return db:close() +end + +function fetch_all_arp() + local db = sqlite3.open(r13db) + local sqlStr = string.format("select * from arp") + local result = {} + for row in db:rows(sqlStr) do + if row then + table.insert(result,{ + ["mac"] = row[1], + ["ip"] = row[2], + ["wifi"] = row[3] + }) + end + end + db:close() + return result +end + +function fetch_arp(mac) + local db = sqlite3.open(r13db) + local sqlStr = string.format("select * from arp where mac = '%s' limit 1", mac) + local result = {} + for row in db:rows(sqlStr) do + if row then + table.insert(result,{ + ["mac"] = row[1], + ["ip"] = row[2], + ["wifi"] = row[3] + }) + end + end + db:close() + return result +end + +function insert_arp_macip(mac, ip, wifi) + local db = sqlite3.open(r13db) + local sqlStr = string.format("delete from ") + sqlStr = string.format("insert into arp values('%s', '%s', %d)", mac, ip, wifi) + db:exec(sqlStr) + return db:close() +end + +function delete_arp_all_mac() + local db = sqlite3.open(r13db) + local sqlStr = string.format("delete from arp") + db:exec(sqlStr) + return db:close() +end + +function update_arp(mac, ip, wifi) + local db = sqlite3.open(r13db) + local sqlStr = string.format("update arp set ip = '%s', wifi = %d where mac = '%s'", ip, wifi, mac) + db:exec(sqlStr) + return db:close() +end + +function init_access_token_table() + local db = sqlite3.open(r13db) + local sqlStr = string.format("create table if not exists accessTokenTable(token varchar(100), expireTime bigint)") + db:exec(sqlStr) + return db:close() +end + +function add_access_token(token, expireTime) + local db = sqlite3.open(r13db) + local sqlStr = string.format("insert into accessTokenTable values('%s', %d)", token, expireTime) + db:exec(sqlStr) + return db:close() +end + +function fetch_access_token() + local db = sqlite3.open(r13db) + local sqlStr = string.format("select * from accessTokenTable") + local result = {} + for row in db:rows(sqlStr) do + if row then + table.insert(result,{ + ["token"] = row[1], + ["expireTime"] = row[2] + }) + end + end + db:close() + return result +end + +function update_access_token(oldToken, newToken, expireTime) + local db = sqlite3.open(r13db) + local sqlStr = string.format("update accessTokenTable set token = '%s', expireTime = %d where token = '%s'", newToken, expireTime, oldToken) + db:exec(sqlStr) + return db:close() +end + +function delete_access_token() + local db = sqlite3.open(r13db) + local sqlStr = string.format("delete from accessTokenTable") + db:exec(sqlStr) + return db:close() +end + +function init_deny_mac_table() + local db = sqlite3.open(r13db) + local sqlStr = string.format("create table if not exists denymac(mac varchar(50))") + db:exec(sqlStr) + return db:close() +end + +function add_deny_mac(mac) + local db = sqlite3.open(r13db) + local sqlStr = string.format("insert into denymac values('%s')", mac) + db:exec(sqlStr) + return db:close() +end + +function fetch_all_deny_mac() + local db = sqlite3.open(r13db) + local sqlStr = string.format("select * from denymac") + local result = {} + for row in db:rows(sqlStr) do + if row then + table.insert(result,{ + ["mac"] = row[1] + }) + end + end + db:close() + return result +end + +function delete_deny_mac(mac) + local db = sqlite3.open(r13db) + local sqlStr = string.format("delete from denymac where mac = '%s'", mac) + db:exec(sqlStr) + return db:close() +end + +function init_ssid_table() + local db = sqlite3.open(r13db) + local sqlStr = string.format("create table if not exists ssid(ssid24 varchar(50), ssid5 varchar(50))") + db:exec(sqlStr) + return db:close() +end + +function add_ssid(ssid24, ssid5) + local db = sqlite3.open(r13db) + local sqlStr = string.format("insert into ssid values('%s', '%s')", ssid24, ssid5) + db:exec(sqlStr) + return db:close() +end + +function fetch_ssid() + local db = sqlite3.open(r13db) + local sqlStr = string.format("select ssid24,ssid5 from ssid limit 1") + local result = {} + for row in db:rows(sqlStr) do + if row then + table.insert(result,{ + ["ssid24"] = row[1], + ["ssid5"] = row[2] + }) + end + end + db:close() + return result +end + +function update_ssid(ssid24, ssid5) + local db = sqlite3.open(r13db) + local sqlStr = string.format("update ssid set ssid24 = '%s', ssid5 = '%s'", ssid24, ssid5) + db:exec(sqlStr) + return db:close() +end + +function initBluetoothTable() + local db = sqlite3.open(r13db) + local sqlStr = string.format("create table if not exists blemeshtable(id varchar(100), mac varchar(100), key varchar(100), name varchar(100), deviceType varchar(100), len varchar(100))") + db:exec(sqlStr) + return db:close() +end + +function addBluetoothDevice(id, mac, key, name, deviceType, len) + local db = sqlite3.open(r13db) + local sqlStr = string.format("insert into blemeshtable values('%s', '%s', '%s', '%s', '%s', '%s')", id, mac, key, name, deviceType, len) + db:busy_handler(database_busy) + db:exec(sqlStr) + return db:close() +end + +function fetchAllBluetoothDevice() + local db = sqlite3.open(r13db) + local sqlStr = string.format("select * from blemeshtable") + db:busy_handler(database_busy) + local result = {} + for row in db:rows(sqlStr) do + if row then + table.insert(result,{ + ["mac"] = row[2], + ["deviceType"] = row[5] + }) + end + end + db:close() + return result +end + +function deleteBluetoothDevice(mac) + local db = sqlite3.open(r13db) + local sqlStr = string.format("delete from blemeshtable where mac = '%s'", mac) + db:busy_handler(database_busy) + db:exec(sqlStr) + return db:close() +end + +function updateBluetoothDevice(id, key, name, len, mac) + local db = sqlite3.open(r13db) + local sqlStr = string.format("update blemeshtable set id = '%s', key = '%s', name = '%s', len = '%s' where mac = '%s'", id, key, name, len, mac) + db:busy_handler(database_busy) + db:exec(sqlStr) + return db:close() +end + +function fetchBluetoothDevice(mac) + local db = sqlite3.open(r13db) + local sqlStr = string.format("select * from blemeshtable where mac = '%s'", mac) + db:busy_handler(database_busy) + local result = {} + for row in db:rows(sqlStr) do + if row then + table.insert(result,{ + ["id"] = row[1], + ["mac"] = row[2], + ["deviceType"] = row[5], + ["name"] = row[4] + }) + end + end + db:close() + return result +end + +function fetchBluetoothDeviceKey() + local db = sqlite3.open(r13db) + local sqlStr = string.format("select * from blemeshtable where key != ''") + db:busy_handler(database_busy) + local result = {} + for row in db:rows(sqlStr) do + if row then + table.insert(result,{ + ["mac"] = row[2], + ["key"] = row[3] + }) + end + end + db:close() + return result +end + +function getBluetoothDevice(id) + local db = sqlite3.open(r13db) + local sqlStr = string.format("select * from blemeshtable where id = '%s'", id) + db:busy_handler(database_busy) + local result = "" + for row in db:rows(sqlStr) do + if row then + result = row[2] + end + end + db:close() + return result +end + +function fetchAllBleMeshDevice() + local db = sqlite3.open(r13db) + local sqlStr = string.format("select * from blemeshtable where id !=''") + db:busy_handler(database_busy) + local result = {} + for row in db:rows(sqlStr) do + if row then + table.insert(result,{ + ["mac"] = row[2], + ["deviceType"] = row[5], + ["name"] = row[4] + }) + end + end + db:close() + return result +end + +function getBleDeviceNameLength(id) + local db = sqlite3.open(r13db) + local sqlStr = string.format("select * from blemeshtable where id = '%s'", id) + db:busy_handler(database_busy) + local result = "" + for row in db:rows(sqlStr) do + if row then + result = row[6] + end + end + db:close() + return result +end \ No newline at end of file diff --git a/Me_Lua/r13/meizu/nwfs.lua b/Me_Lua/r13/meizu/nwfs.lua new file mode 100644 index 0000000..d8e4ba7 --- /dev/null +++ b/Me_Lua/r13/meizu/nwfs.lua @@ -0,0 +1,2804 @@ +module("meizu.nwfs", package.seeall) +--network functions + +local cjson = require "cjson" +local lfs = require "lfs" +local bfs = require "meizu.bfs" +local RC = require "meizu.r10config" +local dbfs = require "meizu.dbfs" + +local bind_router = bfs.bind_router +local data_to_json = bfs.data_to_json +local exec_cmd_in_sh = bfs.exec_cmd_in_sh +local exec_reboot = bfs.exec_reboot +local get_device_SN = bfs.get_device_SN +local get_device_version = bfs.get_device_version +local get_https_data = bfs.get_https_data +local rts_get_access_token = bfs.rts_get_access_token +local set_passwd = bfs.set_passwd +local silent_upgrade = bfs.silent_upgrade +local strsplit = bfs.strsplit +local b64dec = bfs.b64dec +local b64enc = bfs.b64enc + +local lue = require("luci.util").exec + +function wifi_network(wifi_device_name) + local network = require "luci.model.network".init() + local wifi_net = network:get_wifinet(wifi_device_name) + if wifi_net then + local dev = wifi_net:get_device() + if dev then + return { + id = wifi_device_name, + name = wifi_net:shortname(), + up = wifi_net:is_up(), + mode = wifi_net:active_mode(), + ssid = wifi_net:active_ssid(), + bssid = wifi_net:active_bssid(), + encryption = wifi_net:active_encryption(), + encryption_src = wifi_net:get("encryption"), + frequency = wifi_net:frequency(), + bitrate = wifi_net:bitrate(), + ifname = wifi_net:ifname(), + assoclist = wifi_net:assoclist(), + country = wifi_net:country(), + key = wifi_net:get("key"), + key1 = wifi_net:get("key1"), + hidden = wifi_net:get("hidden"), + device = { + up = dev:is_up(), + device = dev:name(), + name = dev:get_i18n() + } + } + end + end + return {} +end + +function wifi_networks() + local result = {} + local network = require "luci.model.network".init() + local dev + for _, dev in ipairs(network:get_wifidevs()) do + local rd = { + up = dev:is_up(), + device = dev:name(), + name = dev:get_i18n(), + networks = {} + } + local wifi_net + for _, wifi_net in ipairs(dev:get_wifinets()) do + rd.networks[#rd.networks+1] = { + name = wifi_net:shortname(), + up = wifi_net:is_up(), + mode = wifi_net:active_mode(), + ssid = wifi_net:active_ssid(), + bssid = wifi_net:active_bssid(), + encryption = wifi_net:active_encryption(), + frequency = wifi_net:frequency(), + channel = wifi_net:channel(), + signal = wifi_net:signal(), + quality = wifi_net:signal_percent(), + noise = wifi_net:noise(), + bitrate = wifi_net:bitrate(), + ifname = wifi_net:ifname(), + assoclist = wifi_net:assoclist(), + country = wifi_net:country(), + key = wifi_net:get("key"), + key1 = wifi_net:get("key1"), + encryption_src = wifi_net:get("encryption"), + hidden = wifi_net:get("hidden"), + } + end + result[#result+1] = rd + end + return result +end + +function nw_get_wifi_settings() + luci.http.prepare_content("application/json") + local app_version = luci.http.formvalue("appVer") + if app_version == nil then + app_version = 0 + end + local list = get_wifi_settings(app_version) + luci.http.write(list) +end + +function ww_get_wifi_settings(app_version) + local list = get_wifi_settings(app_version) + --return data_to_json(list) + return list +end + +function get_wifi_settings(app_version) + local network = require "luci.model.network".init() + local info_list = {} + local wifis = wifi_networks() + for i,wifi_net in ipairs(wifis) do + local item = {} + local index = 1 + + if wifi_net.device == "mt7628" then + local wifi_net_wl0 = network:get_wifinet('mt7628.network1') + item["ssid"] = wifi_net_wl0:get("ssid") + if wifi_net_wl0:get("disabled") == "1" then + item["status"] = "false" + else + item["status"] = "true" + end + end + + if wifi_net.device == "mt7610e" then + local wifi_net_wl1 = network:get_wifinet('mt7610e.network1') + item["ssid"] = wifi_net_wl1:get("ssid") + if wifi_net_wl1:get("disabled") == "1" then + item["status"] = "false" + else + item["status"] = "true" + end + end + + local encryption = wifi_net.networks[index].encryption_src + local key = wifi_net.networks[index].key + if encryption == "wep-open" then + key = wifi_net.networks[index].key1 + if key:len()>4 and key:sub(0,2)=="s:" then + key = key:sub(3) + end + end + local name = "wl0"; + if "rai0" == wifi_net.networks[index].ifname then + name = "wl1" + end + item["name"] = name + --item["ssid"] = wifi_net.networks[index].ssid + if key == nil then + key = "" + end + item["password"] = key + item["encryption"] = encryption + info_list[#wifis+1-i] = item + end + require "MZLog".log(3, debug.getinfo(1).currentline) + require "MZLog".log(3, info_list) + if tonumber(app_version) >= 5 then + return b64enc(cjson.encode(info_list)) + else + return cjson.encode(info_list) + end +end + +function get_wifi_ssids() + local wifis = wifi_networks() + local ssid1 = "" + local ssid2 = "" + for i, wifi_net in ipairs(wifis) do + if i == 1 then + ssid1 = wifi_net.networks[1].ssid + end + if i == 2 then + ssid2 = wifi_net.networks[1].ssid + end + end + + return ssid1, ssid2 +end + +function nw_wifi_settings() + + luci.http.prepare_content("application/json") + local data = luci.http.formvalue("data") + local app_version = luci.http.formvalue("appVer") + if app_version == nil then + app_version = 0 + end + local switch_2g = nil + local switch_5g = nil + local ssid_2g = nil + local ssid_5g = nil + local pwd_2g = nil + local pwd_5g = nil + local encry_2g = nil + local encry_5g = nil + + if tonumber(app_version) >= 5 then + local data = b64dec(data) + data = cjson.decode(data) + + for k, v in pairs(data) do + if v.name == "wl0" then + switch_2g = v.on + ssid_2g = v.ssid + pwd_2g = v.pwd + encry_2g = v.encryption + require "MZLog".log(3, debug.getinfo(1).currentline) + require "MZLog".log(3, debug.getinfo(1).currentline) + elseif v.name == "wl1" then + switch_5g = v.on + ssid_5g = v.ssid + pwd_5g = v.pwd + encry_5g = v.encryption + end + end + else + switch_2g = luci.http.formvalue("on1") + ssid_2g = luci.http.formvalue("ssid1") + pwd_2g = luci.http.formvalue("pwd1") + encry_2g = luci.http.formvalue("encryption1") + + switch_5g = luci.http.formvalue("on2") + ssid_5g = luci.http.formvalue("ssid2") + pwd_5g = luci.http.formvalue("pwd2") + encry_5g = luci.http.formvalue("encryption2") + + end + + dbfs.init_ssid_table() + local dbssid = dbfs.fetch_ssid() + if nil == dbssid or #dbssid == 0 then + dbfs.add_ssid(ssid_2g, ssid_5g) + else + dbfs.update_ssid(ssid_2g, ssid_5g) + end + + local res = wifi_settings(switch_2g, ssid_2g, pwd_2g, encry_2g, switch_5g, ssid_5g, pwd_5g, encry_5g) + luci.http.write_json(res) +end + +function wifi_settings(on1, ssid1, pwd1, encry1, on2, ssid2, pwd2, encry2) + local result = {} + local res = {} + local code = 0 + local code1 = 0 + local code2 = 0 + local details = {} + local code1 = check_ssid(ssid1, 31) + local code2 = check_ssid(ssid2, 31) + local succeed1 = false + local succeed2 = false + if on1 == "false" then + require "MZLog".log(3, debug.getinfo(1).currentline) + succeed1 = set_wifi_basic_info(1, nil, nil, nil, on1) + else + if code1 == 1 then + succeed1 = set_wifi_basic_info(1, ssid1, pwd1, encry1, on1) + end + end + + if on2 == "false" then + succeed2 = set_wifi_basic_info(2, nil, nil, nil, on2) + else + if code2 == 1 then + succeed2 = set_wifi_basic_info(2, ssid2, pwd2, encry2, on2) + end + end + + if code == 0 and code1 ~= 0 and code2 ~= 0 and succeed1 ~= false and succeed2 ~= false then + res["result"] = true + fork_restart_network() + else + res["result"] = false + end + + return res +end + +function set_wifi_basic_info(wifi_index, ssid, password, encryption, on) + + local network = require "luci.model.network".init() + if wifi_index == 1 then + wifi_net = network:get_wifinet('mt7628.network1') + wifi_dev = network:get_wifidev('mt7628') + end + + if wifi_index == 2 then + wifi_net = network:get_wifinet('mt7610e.network1') + wifi_dev = network:get_wifidev('mt7610e') + end + + if wifi_net == nil then + return false + end + + if wifi_dev then + if on == "true" then + wifi_net:set("disabled", "0") + elseif on == "false" then + wifi_net:set("disabled", "1") + end + end + + if not is_str_nil(ssid) and check_ssid(ssid) then + wifi_net:set("ssid",ssid) + end + + local code = check_wifi_passwd(password,encryption) + if code == 0 then + wifi_net:set("encryption",encryption) + wifi_net:set("key",password) + if encryption == "none" then + wifi_net:set("key","") + elseif encryption == "wep-open" then + wifi_net:set("key1","s:"..password) + wifi_net:set("key",1) + end + elseif code > 1502 then + return false + end + + network:save("wireless") + network:commit("wireless") + return true +end + +function is_str_nil(str) + return (str == nil or str == "") +end + +function check_ssid(ssid) + if is_str_nil(ssid) then + return 0 + end + + return 1 +end + +function check_wifi_passwd(passwd,encryption) + if is_str_nil(encryption) or (encryption and encryption ~= "none" and is_str_nil(passwd)) then + return 1502 + end + if encryption == "psk" or encryption == "psk2" then + if passwd:len() < 8 then + return 1520 + end + elseif encryption == "mixed-psk" then + if passwd:len()<8 or passwd:len()>63 then + return 1521 + end + elseif encryption == "wep-open" then + if passwd:len()~=5 and passwd:len()~=13 then + return 1522 + end + end + return 0 +end + +function fork_restart_wifi() + local FORK_RESTART_WIFI = "sleep 1; /sbin/wifi >/dev/null 2>/dev/null; /etc/init.d/minidlna restart; /etc/init.d/samba restart; /usr/bin/gettraffic flush_wl_dev >/dev/null 2>/dev/null" + exec_cmd_in_sh(FORK_RESTART_WIFI) +end + +function fork_restart_network() + local FORK_RESTART_WORK= "/etc/init.d/network restart" + exec_cmd_in_sh(FORK_RESTART_WORK) +end + +function get_lan_ip() + local uci = require("luci.model.uci").cursor() + local lan = uci:get_all("network", "lan") + return lan.ipaddr +end + +--[[ +--function: 定时wifi开关 +--author: rh_Jameson +--]]-- +--wifi重连 &开关基础函数 +local function wifi_reconnect_shutdown(shutdown, wnet) + local netmd = require "luci.model.network".init() + local net = netmd:get_wifinet(wnet) + local dev = net:get_device() + if dev and net then + dev:set("disabled", nil) + net:set("disabled", shutdown and 1 or nil) + netmd:commit("wireless") + + luci.sys.call("env -i /bin/ubus call network reload >/dev/null 2>/dev/null") + + luci.sys.call("env -i /sbin/wifi reload >/dev/null 2>/dev/null") + + luci.http.status(200, shutdown and "Shutdown" or "Reconnected") + + return + end + + luci.http.status(404, "No such radio") +end +--wifi重连 +function wifi_reconnect(wnet) + if(getstate) + wifi_reconnect_shutdown(false, wnet) +end +--wifi开关 +function wifi_shutdown(wnet) + wifi_reconnect_shutdown(true, wnet) +end + + + + + + + +function macFormat(mac) + if mac then + return string.upper(string.gsub(mac, "-", ":")) + else + return "" + end +end + +function getAllWifiConnetDeviceDict() + local result = {} + for index = 1,2 do + local wifilist = getWifiConnectDeviceList(index) + for _, mactime in pairs(wifilist) do + local item = {} + item["wifiIndex"] = index + item["time"] = mactime["time"] + item["rx"] = mactime["rx"] + item["tx"] = mactime["tx"] + result[macFormat(mactime["mac"])] = item + end + end + return result +end + +function getWifiConnectDeviceList(wifiIndex) + local dlist = {} + local macfile = nil + local uci = require("luci.model.uci").cursor() + if tonumber(wifiIndex) == 1 then + local disable_2g = uci.get("wireless", "mt7628iface", "disabled") + if "1" ~= disable_2g then + local cmd = [[iwpriv ra0 get_mac_table]] + macfile = io.popen(cmd) + end + elseif tonumber(wifiIndex) == 2 then + local disable_5g = uci.get("wireless", "mt7610eiface", "disabled") + if "1" ~= disable_5g then + local cmd = [[iwpriv rai0 get_mac_table]] + macfile = io.popen(cmd) + end + end + local tmplinenumber = 0 + if nil ~= macfile then + for line in macfile:lines() do + if 0 ~= tmplinenumber and "" ~= line then + local item = {} + local mactime = strsplit(line, " ") + item["mac"] = macFormat(mactime[1]) + item["time"] = mactime[2] + item["rx"] = mactime[3] + item["tx"] = mactime[4] + table.insert(dlist,item) + end + tmplinenumber = tmplinenumber + 1 + end + end + return dlist +end + +function getDHCPLists() + local NixioFs = require("nixio.fs") + local LuciUci = require("luci.model.uci") + local uci = LuciUci.cursor() + local result = {} + local leasefile = "/var/dhcp.leases" + uci:foreach("dhcp", "dnsmasq", + function(s) + if s.leasefile and NixioFs.access(s.leasefile) then + leasefile = s.leasefile + return false + end + end) + local dhcp = io.open(leasefile, "r") + if dhcp then + for line in dhcp:lines() do + if line then + local ts, mac, ip, name = line:match("^(%d+) (%S+) (%S+) (%S+)") + if name == "*" then + name = "" + end + if ts and mac and ip and name then + result[#result+1] = { + mac = macFormat(mac), + ip = ip, + name = name, + sp = 0 + } + end + end + end + dhcp:close() + return result + else + return false + end +end + +function getDHCPDict() + local dhcpDict = {} + local dhcpList = getDHCPLists() + for _,value in ipairs(dhcpList) do + dhcpDict[value.mac] = value + end + return dhcpDict +end + +function getDHCPIpDicts() + local dhcpDict = {} + local dhcpList = getDHCPLists() + for _,value in ipairs(dhcpList) do + dhcpDict[value.ip] = value + end + return dhcpDict +end + +function getDeviceInfoFromDB() + local result = {} + local deviceList = dbfs.fetchAllDeviceInfo() + if #deviceList > 0 then + for _, device in ipairs(deviceList) do + result[device.mac] = device + end + end + return result +end + +function is_device_online(ip) + local lu = require("luci.util") + local cmd = "ping -W 2 -c 1 " .. ip .. " > /dev/null ;echo -n $?" + local pingresult = lu.exec(cmd) + + local res = nil + if pingresult == "0" then + res = true + else + res = false + + end + + return res +end + +function get_connect_device_list_router() + local devicelist = {} + dbfs.init_arp_table() + local dbarp = dbfs.fetch_all_arp() + + local deviceDBDict = getDeviceInfoFromDB() + local dhcpDeviceDict = getDHCPDict() + local blacklist0 = dbfs.fetch_all_deny_mac() + local wifiDeviceDict = getAllWifiConnetDeviceDict() + + local cmd = [[cat /proc/net/arp |grep br-lan|awk '{print $1","$4}']] + local ipmacs = {} + local devs = lue(cmd) + if devs ~= "" then + local ipmacstr = strsplit(devs, '\n') + ipmacstr[#ipmacstr] = nil + for k, v in pairs(ipmacstr) do + local ipmac = strsplit(v, ',') + ipmacs[string.upper(ipmac[2])] = ipmac + end + end + + for k, v in pairs(dbarp) do + local item = {} + local mac = string.upper(v["mac"]) + if "00:00:00:00:00:00" ~= mac then + local devflag = false + local im = ipmacs[mac] + local wf = v["wifi"] + if wf == 0 then + item["type"] = "wire" + elseif wf == 1 then + item["type"] = "2.4G" + elseif wf == 2 then + item["type"] = "5G" + end + if nil ~= im then + item["ip"] = im[1] + item["mac"] = string.upper(im[2]) + else + item["ip"] = v["ip"] + item["mac"] = mac + end + + local deviceDB = deviceDBDict[mac] + local dhcpinfo = dhcpDeviceDict[mac] + if deviceDB ~= nil then + item["devicename"] = deviceDB.orgname + if deviceDB.devicename ~= '' then + item["devicename"] = deviceDB.devicename + end + elseif dhcpinfo ~= nil then + item["devicename"] = dhcpinfo.name + dbfs.saveDeviceInfo(mac, dhcpinfo.name, "", dhcpinfo.ip) + else + item["devicename"] = "unknown" + end + + local isblack = false + if nil ~= blacklist0 then + for k, v in pairs(blacklist0) do + if v.mac == mac then + isblack = true + end + end + end + if isblack == false then + item["enable"] = true + else + item["enable"] = false + end + + local wifi = wifiDeviceDict[mac] + + item["online"] = false + if wf == 0 and nil ~= im then + item["online"] = true + end + if wifi ~= nil then + item["online"] = true + if wifi.wifiIndex == 1 then + item["type"] = "2.4G" + elseif wifi.wifiIndex == 2 then + item["type"] = "5G" + end + end + if nil == item["type"] or false == item["online"] then + item["type"] = "unknown" + end + table.insert(devicelist,item) + end + end + return devicelist +end + +function get_connect_device_list() + local LuciNetwork = require("luci.model.network").init() + local lanNetwork = LuciNetwork:get_network("lan") + local apc = lanNetwork:get_option_value("apclient") + local devicelist = {} + if nil == apc or "" == apc then + --mode router + devicelist = get_connect_device_list_router() + else + --mode apclient + if "mt7628" == apc then + + elseif "mt7610e" == apc then + + end + devicelist = get_connect_device_list_router() + end + return devicelist +end + +function ww_get_connect_device_list() + local result = get_connect_device_list() + return result +end + +function nw_get_connect_device_list() + luci.http.prepare_content("application/json") + local result = get_connect_device_list() + luci.http.write_json(result) +end + +function nw_set_device_name() + local mac = luci.http.formvalue("mac") + local devicename = luci.http.formvalue("devicename") + set_device_name(mac,devicename) +end + +function set_device_name(mac,devicename) + local code = 0 + local deviceList = {} + + if is_str_nil(mac) or is_str_nil(devicename) then + code = 1502 + else + code = save_device_name(mac,devicename) + end + return code +end + +function save_device_name(mac,name) + local code = 0 + local code = dbfs.updateDeviceNickname(macFormat(mac),name) + if code == 0 then + return true + else + return false + end +end + +function set_wan_switch(mac, mode, enable) + local result = {} + local code = false + if is_str_nil(mac) then + return + else + mac = macFormat(mac) + enable = tonumber(enable) + end + + local dbdenymac = dbfs.fetch_all_deny_mac() + local macstr = "" + local deny = false + if enable == 0 then + if nil == dbdenymac then + dbfs.add_deny_mac(mac) + macstr = mac + else + if #dbdenymac >= 60 then + return "black person reach max" + end + for _, macaddr in ipairs(dbdenymac) do + if mac == macaddr.mac then + return "same black" + else + macstr = macstr.." "..macaddr.mac + end + end + dbfs.add_deny_mac(mac) + macstr = macstr.." "..mac + end + else + if nil == dbdenymac then + return + end + for _, macaddr in ipairs(dbdenymac) do + if mac == macaddr.mac then + dbfs.delete_deny_mac(mac) + if #dbdenymac == 1 then + deny = true + end + else + macstr = macstr.." "..macaddr.mac + end + end + end + local cmd = [[wireless-ban.sh ]] + if deny == true then + cmd = cmd.."none" + else + cmd = cmd.."deny "..macstr + end + exec_cmd_in_sh(cmd) + code = true + result["result"] = code + + return result +end + +function nw_set_wan_switch() + local result = {} + local code = false + local mac = luci.http.formvalue("mac") + local mode = luci.http.formvalue("mode") + local enable = luci.http.formvalue("enable") + + code = set_wan_switch(mac,mode,enable) + result["result"] = code + luci.http.write_json(result) +end + +function getNetConnect(ip) + local sys = require "luci.sys" + local res = {} + res["bytes"] = 0 + local conn = sys.net.conntrack() + require "MZLog".log(3, debug.getinfo(1).currentline) + require "MZLog".log(3, debug.getinfo(1).currentline) + require "MZLog".log(3, conn) + for _, value in pairs(conn) do + if value.src == ip then + res["bytes"] = value.bytes + res["bytes"] + end + end + return res +end + +function get_net_device() + local ntm = require "luci.model.network".init() + local dev + local devices = { } + for _, dev in luci.util.vspairs(luci.sys.net.devices()) do + if dev ~= "lo" and not ntm:ignore_interface(dev) then + devices[#devices+1] = dev + end + end + local curdev = luci.dispatcher.context.requestpath + curdev = curdev[#curdev] ~= "bandwidth" and curdev[#curdev] or devices[1] + luci.http.write_json(devices) +end + +function nw_check_sys_password() + local password = luci.http.formvalue("password") + check_sys_password(password) +end + +function check_sys_password(password) + local result={} + if not is_str_nil(password) then + local check = check_sys_pwd(password) + if check then + code = true + else + code = false + end + end + result["result"] = code + luci.http.write_json(result) +end + +function get_wan_type() + local LuciNetwork = require("luci.model.network").init() + local lanNetwork = LuciNetwork:get_network("lan") + local wanNetwork = LuciNetwork:get_network("wan") + local wanDetails = {} + if wanNetwork and lanNetwork then + local apc = lanNetwork:get_option_value("apclient") + if nil ~= apc and "" ~= apc then + wanDetails["type"] = "apclient" + local uci = require("luci.model.uci").cursor() + local ssid = uci.get("wireless", apc, "apclissid") + wanDetails["ssid"] = ssid + else + local wanType = wanNetwork:proto() + + if wanType == "static" then + wanDetails["ipaddr"] = wanNetwork:get_option_value("ipaddr") + wanDetails["netmask"] = wanNetwork:get_option_value("netmask") + wanDetails["gateway"] = wanNetwork:get_option_value("gateway") + elseif wanType == "pppoe" then + wanDetails["type"] = "pppoe" + wanDetails["pppoeName"] = wanNetwork:get_option_value("username") + wanDetails["pppoePwd"] = wanNetwork:get_option_value("password") + elseif wanType == "dhcp" then + wanDetails["type"] = "dhcp" + wanDetails["pppoeName"] = "" + wanDetails["pppoePwd"] = "" + end + end + return wanDetails + else + return nil + end +end + +function ww_get_wan_type() + local result = get_wan_type() + return data_to_json(result) +end + +function nw_get_wan_type() + local result = get_wan_type() + luci.http.write_json(result) +end +function nw_set_wan_type() + local wan_type = luci.http.formvalue("type") + if "apclient" == wan_type then + local channel = luci.http.formvalue("channel") + local ssid = luci.http.formvalue("ssid") + local mac = luci.http.formvalue("mac") + local sec = luci.http.formvalue("sec") + local extch = luci.http.formvalue("extch") + local wl_type_val = luci.http.formvalue("aptype") + local key = luci.http.formvalue("key") + set_ap_client(channel, ssid, mac, sec, extch, wl_type_val, key) + else + local pppoe_name = luci.http.formvalue("pppoeName") + local pppoe_pwd = luci.http.formvalue("pppoePwd") + set_wan_type(wan_type, pppoe_name, pppoe_pwd) + end +end + +function set_wan_type(wan_type, pppoe_name, pppoe_pwd) + local result = {} + local code = true + local needRestartWifi = false + if not is_str_nil(wan_type) then + local succeed + if wan_type == "pppoe" and not is_str_nil(pppoe_name) and not is_str_nil(pppoe_pwd) then + succeed = setWanPPPoE(pppoe_name, pppoe_pwd) + elseif wan_type == "dhcp" then + succeed = setWanDHCP() + end + if not succeed then + code = false + else + cancelapclient() + needRestartWifi = true + end + end + result["result"] = code + local dbssid = dbfs.fetch_ssid() + if nil ~= dbssid then + for k, v in pairs(dbssid) do + result["ssid"] = v.ssid24 + end + end + local macaddrcmd = "eth_mac r wl0" + local macaddr = lue(macaddrcmd) + if macaddr ~= "" then + local ipmacstr = strsplit(macaddr, '\n') + ipmacstr[#ipmacstr] = nil + for k, v in pairs(ipmacstr) do + result["mac"] = v + end + end + luci.http.write_json(result) + + if needRestartWifi then + cmd = [[sleep 1;apcli_connect.sh restart &]] + exec_cmd_in_sh(cmd) + --fork_restart_network() + end + luci.http.close() +end + +function set_ap_client(channel, ssid, mac, sec, extch, wl_type_val, key) + local sec_alg = "TKIP" + if string.find(sec, "AES") then + sec_alg = "AES" + end + if string.find(sec, "WPA2") then + sec = "WPA2PSK" + else + if string.find(sec, "WPA") then + sec = "WPA1PSK" + else + sec = "WEP" + end + end + local wl_type = "ra0" + if wl_type_val == "5G" then + wl_type = "rai0" + end + local cmd = [[apcli_connect.sh ]]..wl_type.." "..channel.." "..ssid.." " + cmd = cmd..sec.." "..sec_alg.." "..key + lue(cmd) + require "MZLog".log(3, cmd) + local ret = {} + ret["result"] = true + local ssid5,ssid2 = get_wifi_ssids() + local macaddrcmd = "" + if wl_type_val == "2.4G" then + ret["ssid"] = ssid2 + macaddrcmd = "eth_mac r wl0" + else + ret["ssid"] = ssid5 + macaddrcmd = "eth_mac r wl1" + end + local macaddr = lue(macaddrcmd) + if macaddr ~= "" then + local ipmacstr = strsplit(macaddr, '\n') + ipmacstr[#ipmacstr] = nil + for k, v in pairs(ipmacstr) do + ret["mac"] = v + end + end + luci.http.prepare_content("application/json") + luci.http.write_json(ret) + local cmd = [[sleep 1;apcli_connect.sh restart &]] + exec_cmd_in_sh(cmd) + luci.http.close() +end + +function cancelapclient() + local LuciNetwork = require("luci.model.network").init() + local lanNetwork = LuciNetwork:get_network("lan") + local apc = lanNetwork:get_option_value("apclient") + if nil ~= apc then + local dbssid = dbfs.fetch_ssid() + local ssid_2g = "" + local ssid_5g = "" + if nil ~= dbssid then + for k, v in pairs(dbssid) do + ssid_2g = v.ssid24 + ssid_5g = v.ssid5 + end + end + local cmd = [[apcli_connect.sh disable]] + if "" ~= ssid_2g and "" ~= ssid_5g then + cmd = cmd.." "..ssid_2g.." "..ssid_5g + end + lue(cmd) + end +end + +function setWanPPPoE(name, password) + local LuciNetwork = require("luci.model.network").init() + local uci = require("luci.model.uci").cursor() + local iface = "wan" + local ifname = getWanEth() + local oldconf = uci:get_all("network", "wan") or {} + local wanrestart = true + if oldconf.username == name and oldconf.password == password then + wanrestart = false + end + local wanNet = LuciNetwork:del_network(iface) + local mtuvalue = 1480 + wanNet = LuciNetwork:add_network( + iface, { + proto ="pppoe", + ifname = ifname, + username = name, + password = password, + mtu = mtuvalue + }) + if wanNet then + LuciNetwork:save("network") + LuciNetwork:commit("network") + if wanrestart then + wanRestart() + end + return true + else + return false + end +end + +function setWanDHCP() + local LuciNetwork = require("luci.model.network").init() + local uci = require("luci.model.uci").cursor() + local oldconf = uci:get_all("network", "wan") or {} + local iface = "wan" + local ifname = getWanEth() + local wanrestart = true + + local wanNet = LuciNetwork:del_network(iface) + if oldconf.proto == "dhcp" then + wanrestart = false + end + local network = { + proto = "dhcp", + ifname = ifname + } + wanNet = LuciNetwork:add_network(iface, network) + if wanNet then + LuciNetwork:save("network") + LuciNetwork:commit("network") + if wanrestart then + wanRestart() + end + return true + else + return false + end +end + +function check_sys_pwd(oldPassword) + local LuciSys = require("luci.sys") + return LuciSys.user.checkpasswd("root", oldPassword) +end + +function getWanEth() + local LuciNetwork = require("luci.model.network").init() + local wanNetwork = LuciNetwork:get_network("wan") + return wanNetwork:get_option_value("ifname") +end + +function wanRestart() + local LuciUtil = require("luci.util") + LuciUtil.exec("env -i /sbin/ifup wan") +end + +function netspeed_channel(cmd) + local speed_table = {} + local speed_file = io.popen(cmd) + for line in speed_file:lines() do + table.insert(speed_table, line) + end + speed_file:close() + + local rx = 0 + local tx = 0 + local speed, tb, rxb, txb + local n = 0 + for k, v in ipairs(speed_table) do + speed = strsplit(v, ',') + if (k == 1) then + tb = tonumber(speed[1]) + rxb = tonumber(speed[3]) + txb = tonumber(speed[2]) + else + n = n + 1 + local tmp + local td + tmp = tonumber(speed[1]) + td = tmp - tb + tb = tmp + + tmp = tonumber(speed[3]) + rx = rx + (tmp - rxb) / td + rxb = tmp + + tmp = tonumber(speed[2]) + tx = tx + (tmp - txb) / td + txb = tmp + end + end + rx = string.format("%6.2f", rx/n) + tx = string.format("%6.2f", tx/n) + return rx, tx +end + +function real_time_net_speed() + local res = {} + --local cmd = [[luci-bwc -i eth0.2|tail -n 5|sed -e 's#.*\[\s*\(.*\)\s*\].*#\1#']] + local uci = require("luci.model.uci").cursor() + local apclient = uci.get("network", "lan", "apclient") + + local cmd = nil + local rx = 0 + local tx = 0 + if nil == apclient then + cmd = [[luci-bwc -i eth0.2 |tail -n 5 |sed -e 's#.*\[\s*\(.*\)\s*\].*#\1#']] + rx, tx = netspeed_channel(cmd) + else + cmd = [[luci-bwc -i eth0.1 |tail -n 5 |sed -e 's#.*\[\s*\(.*\)\s*\].*#\1#']] + rx, tx = netspeed_channel(cmd) + cmd = [[luci-bwc -i ra0 |tail -n 5 |sed -e 's#.*\[\s*\(.*\)\s*\].*#\1#']] + local tmprx, tmptx = netspeed_channel(cmd) + rx = rx + tmprx + tx = tx + tmptx + cmd = [[luci-bwc -i rai0 |tail -n 5 |sed -e 's#.*\[\s*\(.*\)\s*\].*#\1#']] + tmprx, tmptx = netspeed_channel(cmd) + rx = rx + tmprx + tx = tx + tmptx + end + + res["rx"] = rx + res["tx"] = tx + + return res +end + +function get_device_details(mac) + dbfs.change_maclist_table() + local item = {} + mac = string.upper(mac) + local deviceDBDict = getDeviceInfoFromDB() + local dhcpDeviceDict = getDHCPDict() + local blacklist0 = dbfs.fetch_all_deny_mac() + local wifiDeviceDict = getAllWifiConnetDeviceDict() + + local cmd = [[cat /proc/net/arp |grep br-lan|awk '{print $1","$4}']] + local ipmacs = {} + local devs = lue(cmd) + if devs ~= "" then + local ipmacstr = strsplit(devs, '\n') + ipmacstr[#ipmacstr] = nil + for k, v in pairs(ipmacstr) do + local ipmac = strsplit(v, ',') + ipmacs[string.upper(ipmac[2])] = ipmac + end + end + + item["diskaccess"] = false + item["speed"] = 0 + item["upload"] = 0 + item["download"] = 0 + item["time"] = 0 + + local isblack = false + if nil ~= blacklist0 then + for k, v in pairs(blacklist0) do + if v.mac == mac then + isblack = true + end + end + end + if isblack == false then + item["enable"] = true + else + item["enable"] = false + end + + local dbarp = dbfs.fetch_arp(mac) + if nil ~= dbarp then + for k, v in pairs(dbarp) do + local im = ipmacs[mac] + local wf = v["wifi"] + if wf == 0 then + item["type"] = "wire" + elseif wf == 1 then + item["type"] = "2.4G" + elseif wf == 2 then + item["type"] = "5G" + end + if nil ~= im then + item["ip"] = im[1] + item["mac"] = string.upper(im[2]) + else + item["ip"] = v["ip"] + item["mac"] = mac + end + + local deviceDB = deviceDBDict[mac] + local dhcpinfo = dhcpDeviceDict[mac] + if deviceDB ~= nil then + item["devicename"] = deviceDB.orgname + if deviceDB.devicename ~= '' then + item["devicename"] = deviceDB.devicename + end + else + item["devicename"] = dhcpinfo.name + dbfs.saveDeviceInfo(mac, dhcpinfo.name, "", dhcpinfo.ip) + end + + local wifi = wifiDeviceDict[mac] + + item["online"] = false + if wf == 0 and nil ~= im then + item["online"] = true + end + if wifi ~= nil then + item["online"] = true + local time = wifi.time + item["time"] = time + local rx = wifi.rx + local tx = wifi.tx + item["upload"] = tx + item["download"] = rx + if wifi.wifiIndex == 1 then + item["type"] = "2.4G" + elseif wifi.wifiIndex == 2 then + item["type"] = "5G" + end + end + if nil == item["type"] or false == item["online"] then + item["type"] = "unknown" + end + end + end + + require "MZLog".log(3, debug.getinfo(1).currentline) + require "MZLog".log(3, item) + require "MZLog".log(3, debug.getinfo(1).currentline) + + return item +end + +function nw_get_device_details() + local mac = luci.http.formvalue("mac") + local res = get_device_details(mac) + luci.http.write_json(res) +end + +function ww_get_device_details(mac) + local res = get_device_details(mac) + return res +end + +function get_wireless_channel() + local uci = require("luci.model.uci").cursor() + local channel = uci.get("wireless", "mt7628", "channel") + local bw = uci.get("wireless", "mt7628", "bw") + local ret = {} + ret["channel"] = channel + ret["extch"] = bw + return ret +end + +function nw_get_wireless_channel() + local ret = get_wireless_channel() + luci.http.write_json(ret) +end + +function ww_get_wireless_channel() + local ret = get_wireless_channel() + return cjson.encode(ret) +end + +function set_wireless_channel(channel, extch) + local res = false + local uci = require("luci.model.uci").cursor() + local c = tonumber(channel) + + if c >= 0 and c <= 13 then + res = uci.set("wireless", "mt7628", "channel", c) + if extch == "1" or extch == "0" then + res = uci.set("wireless", "mt7628", "bw", extch) + end + end + uci.commit("wireless") + fork_restart_wifi() + return res +end + +function nw_set_wireless_channel(channel, extch) + local channel = luci.http.formvalue("channel") + local extch = luci.http.formvalue("extch") + local res = set_wireless_channel(channel, extch) + local ret = {} + ret["result"] = res + luci.http.write_json(ret) +end + +function ww_set_wireless_channel(channel, extch) + local res = set_wireless_channel(channel, extch) + local ret = {} + ret["result"] = res + return ret +end + +function is_wan_connected() + local lu = require("luci.util") + local cmd = "ping -W 2 -c 1 www.baidu.com > /dev/null ;echo -n $?" + local pingresult = lu.exec(cmd) + + local res = {} + if pingresult == "0" then + res["result"] = true + else + res["result"] = false + + end + + return res +end + +function set_bluetooth(id, status) + require "MZLog".log(3, debug.getinfo(1).currentline) + local res = {} + + if status == "open" then + local cmd = "/root/spi_open" .. id + lue(cmd) + res["result"] = "open" + elseif status == "close" then + local cmd = "/root/spi_close" .. id + lue(cmd) + res["result"] = "close" + end + require "MZLog".log(3, res) + require "MZLog".log(3, debug.getinfo(1).currentline) + return res +end + +function get_bluetooth_info() + --local value = luci.http.formvalue("data") + local value = "010101010101" + local ret = {} + require "MZLog".log(3, debug.getinfo(1).currentline) + local types = string.format("%d", "0x" .. string.sub(value, 1, 2)) + --[[ + if types == "00" then + end + ]] + local id = string.format("%d", "0x" .. string.sub(value, 3, 4)) + local status = string.format("%d", "0x" .. string.sub(value, 5, 6)) + local temp = string.format("%d", "0x" .. string.sub(value, 7, 8)) + local rh = string.format("%d", "0x" .. string.sub(value, 9, 10)) + local light = string.format("%d", "0x" .. string.sub(value, 11, 12)) + + ret["type"] = types + ret["id"] = id + ret["status"] = status + ret["temp"] = temp + ret["rh"] = rh + ret["light"] = light + require "MZLog".log(3, debug.getinfo(1).currentline) + require "MZLog".log(3, ret) + return ret +end + +function nw_set_bluetooth() + require "MZLog".log(3, debug.getinfo(1).currentline) + local id = luci.http.formvalue("id") + local status = luci.http.formvalue("status") + local res = set_bluetooth(id, status) + require "MZLog".log(3, debug.getinfo(1).currentline) + luci.http.write_json(res) +end + +function nw_get_bluetooth_info() + require "MZLog".log(3, debug.getinfo(1).currentline) + local res = get_bluetooth_info() + require "MZLog".log(3, debug.getinfo(1).currentline) + luci.http.write_json(res) +end + +function ww_set_bluetooth() + require "MZLog".log(3, debug.getinfo(1).currentline) + local res = set_bluetooth() + require "MZLog".log(3, debug.getinfo(1).currentline) + return res +end + +function ww_get_bluetooth_info() + require "MZLog".log(3, debug.getinfo(1).currentline) + local res = get_bluetooth_info() + require "MZLog".log(3, debug.getinfo(1).currentline) + return res +end + +------------------------ bluetooth -------------------- + +----------------------- receive data -------------------- +function bluetooth_info() + dbfs.initBluetoothTable() + local value = luci.http.formvalue("data") + require "MZLog".log(3, debug.getinfo(1).currentline) + require "MZLog".log(3, value) + local ret = {} + local types = string.sub(value, 1, 2) + + if types == "00" then + local id = string.sub(value, 3, 4) + local status = string.format("%d", "0x" .. string.sub(value, 5, 6)) + local temp1 = string.sub(value, 7, 8) + local temp2 = string.sub(value, 9, 10) + local temp = temp2..temp1 + temp = string.format("%d", "0x" .. temp) + local rh1 = string.sub(value, 11, 12) + local rh2 = string.sub(value, 13, 14) + local rh = rh2..rh1 + rh = string.format("%d", "0x" .. rh) + local light1 = string.sub(value, 15, 16) + local light2 = string.sub(value, 17, 18) + local light = light2..light1 + light = string.format("%d", "0x" .. light) + local mac = dbfs.getBluetoothDevice(id) + local TMP = "/tmp/"..mac.."0" + + ret["mac"] = mac + ret["id"] = id + if status == "1" then + ret["onoff"] = "on" + else + ret["onoff"] = "off" + end + local timer_id = "" + local flag = "" + local start = "" + local ends = "" + local fd = io.open(TMP, "r") + + if fd then + local res = fd:read() + fd:close() + res = cjson.decode(res) + timer_id = res.timerId + flag = res.flag + start = res.start + ends = res.ends + end + + ret["timerId"] = timer_id + ret["flag"] = flag + ret["start"] = start + ret["ends"] = ends + ret["temp"] = temp + ret["hemi"] = rh + ret["light"] = light + ret["time"] = os.time() + + local result = data_to_json(ret) + --[[ + require "MZLog".log(3, debug.getinfo(1).currentline) + require "MZLog".log(3, id) + require "MZLog".log(3, result) + require "MZLog".log(3, ret) + ]] + local fd = assert(io.open(TMP, "w")) + fd:write(result) + fd:close() + + elseif types == "01" then + local id = string.sub(value, 3, 4) + local voltage1 = string.sub(value, 5, 6) + local voltage2 = string.sub(value, 7, 8) + local voltage = voltage2..voltage1 + voltage = string.format("%d", "0x" .. voltage) + local electricity1 = string.format("%d", "0x" .. string.sub(value, 9, 10)) + local electricity2 = string.format("%d", "0x" .. string.sub(value, 11, 12)) + local electricity = electricity2..electricity1 + electricity = string.format("%d", "0x" .. electricity) + local power1 = string.sub(value, 13, 14) + local power2 = string.sub(value, 15, 16) + local power = power2..power1 + power = string.format("%d", "0x" .. power) + local electric1 = string.sub(value, 17, 18) + local electric2 = string.sub(value, 19, 20) + local electric = electric2..electric1 + electric = string.format("%d", "0x" .. electric) + local mac = dbfs.getBluetoothDevice(id) + local TMP = "/tmp/"..mac.."1" + local timer_id = "" + local fd = io.open(TMP, "r") + if fd then + local res = fd:read() + fd:close() + res = cjson.decode(res) + timer_id = res.timerId + end + + ret["timerId"] = timer_id + ret["mac"] = mac + ret["id"] = id + ret["voltage"] = voltage + ret["current"] = electricity + ret["power"] = power + ret["energy"] = electric + + local result = data_to_json(ret) + local fd = assert(io.open(TMP, "w")) + fd:write(result) + fd:close() + + elseif types == "02" then + require "MZLog".log(3, debug.getinfo(1).currentline) + local id = string.sub(value, 3, 4) + local TYPE = string.sub(value, 5, 6) + local mac1 = string.sub(value, 7, 8) + local mac2 = string.sub(value, 9, 10) + local mac3 = string.sub(value, 11, 12) + local mac4 = string.sub(value, 13, 14) + local mac5 = string.sub(value, 15, 16) + local mac6 = string.sub(value, 17, 18) + local mac = mac6..mac5..mac4..mac3..mac2..mac1 + + mac = string.upper(mac) + local ID = "" + local res = dbfs.fetchBluetoothDevice(mac) + require "MZLog".log(3, debug.getinfo(1).currentline) + require "MZLog".log(3, #res) + require "MZLog".log(3, res) + if #res > 0 then + for k, v in pairs(res) do + ID = v.id + end + end + if id ~= ID then + dbfs.updateBluetoothDevice(id, "", "", "", mac) + end + local TMP = "/tmp/"..mac.."0" + local fd = io.open(TMP, "r") + + if fd then + local res = fd:read() + fd:close() + res = cjson.decode(res) + res["time"] = os.time() + res = cjson.encode(res) + local fd = io.open(TMP, "w") + fd:write(res) + fd:close() + end + require "MZLog".log(3, id) + require "MZLog".log(3, mac) + require "MZLog".log(3, debug.getinfo(1).currentline) + + elseif types == "09" then + local deviceType = string.sub(value, 3, 4) + local mac1 = string.sub(value, 5, 6) + local mac2 = string.sub(value, 7, 8) + local mac3 = string.sub(value, 9, 10) + local mac4 = string.sub(value, 11, 12) + local mac5 = string.sub(value, 13, 14) + local mac6 = string.sub(value, 15, 16) + local mac = mac6..mac5..mac4..mac3..mac2..mac1 + mac = string.upper(mac) + + local res = dbfs.fetchBluetoothDevice(mac) + local ret = nil + local id = nil + if #res > 0 then + for k, v in pairs(res) do + ret = v.mac + id = v.id + end + end + if id then + dbfs.updateBluetoothDevice("", "", "", "", mac) + end + if ret == nil then + dbfs.addBluetoothDevice("", mac, "", "", deviceType, "") + local TMP = "/tmp/"..mac + local fd = io.open(TMP, "w") + fd:write(os.time()) + fd:close() + else + local TMP = "/tmp/"..mac + local fd = io.open(TMP, "w") + fd:write(os.time()) + fd:close() + end + + elseif types == "04" then + local data = string.format("%d", "0x" .. string.sub(value, 3, 4)) + ret["data"] = data + + elseif types == "03" then + require "MZLog".log(3, debug.getinfo(1).currentline) + local id = string.sub(value, 3, 4) + local flag = string.sub(value, 5, 6) + local timer_id = string.sub(value, 7, 8) + local start = string.sub(value, 9, 16) + local ends = string.sub(value, 17, 24) + local mac = dbfs.getBluetoothDevice(id) + local TMP = "/tmp/"..mac.."1" + local fd = io.open(TMP, "r") + + if fd then + require "MZLog".log(3, debug.getinfo(1).currentline) + require "MZLog".log(3, timer_id) + require "MZLog".log(3, flag) + local res = fd:read() + res = cjson.decode(res) + if timer_id == res["timerId"] then + local TMP = "/tmp/"..mac.."0" + local fd = io.open(TMP, "r") + local res = fd:read() + res = cjson.decode(res) + res["flag"] = flag + res["timerId"] = timer_id + res["start"] = start + res["ends"] = ends + res = cjson.encode(res) + local fd = io.open(TMP, "w") + fd:write(res) + fd:close() + end + end + + elseif types == "06" then + require "MZLog".log(3, debug.getinfo(1).currentline) + require "MZLog".log(3, debug.getinfo(1).currentline) + local id = string.sub(value, 3, 4) + local mac = dbfs.getBluetoothDevice(id) + local len = tonumber(dbfs.getBleDeviceNameLength(id)) + local str = string.sub(value, 5, len + 4) + --local res = {} + --[[ + for i = 1, #str, 2 do + res[#res + 1] = (tonumber(string.format("%d", "0x"..string.sub(str, i, i+1)))) + end + ]] + local device_name = str + --[[ + if #res == 1 then + device_name = string.char(res[1]) + elseif #res == 2 then + device_name = string.char(res[1], res[2]) + elseif #res == 3 then + device_name = string.char(res[1], res[2], res[3]) + elseif #res == 4 then + device_name = string.char(res[1], res[2], res[3], res[4]) + elseif #res == 5 then + device_name = string.char(res[1], res[2], res[3], res[4], res[5]) + elseif #res == 6 then + device_name = string.char(res[1], res[2], res[3], res[4], res[5], res[6]) + elseif #res == 7 then + device_name = string.char(res[1], res[2], res[3], res[4], res[5], res[6], res[7]) + elseif #res == 8 then + device_name = string.char(res[1], res[2], res[3], res[4], res[5], res[6], res[7], res[8]) + elseif #res == 9 then + device_name = string.char(res[1], res[2], res[3], res[4], res[5], res[6], res[7], res[8], res[9]) + elseif #res == 10 then + device_name = string.char(res[1], res[2], res[3], res[4], res[5], res[6], res[7], res[8], res[9], res[10]) + end + ]] + require "MZLog".log(3, debug.getinfo(1).currentline) + require "MZLog".log(3, str) + require "MZLog".log(3, device_name) + dbfs.updateBluetoothDevice(id, "", device_name, len, mac) + require "MZLog".log(3, debug.getinfo(1).currentline) + + elseif types == "07" then + local data = string.format("%d", "0x" .. string.sub(value, 3, 4)) + ret["data"] = data + + elseif types == "0b" then + local key_ack = string.sub(value, 3, 4) + local TMP = "/tmp/0b0b" + local fd = io.open(TMP, "w") + fd:write(key_ack) + fd:close() + + elseif types == "0c" then + local id = string.sub(value, 3, 4) + local status = string.sub(value, 5, 6) + local mac = dbfs.getBluetoothDevice(id) + local TMP = "/tmp/"..mac.."0" + local fd = io.open(TMP, "r") + local res = nil + require "MZLog".log(3, debug.getinfo(1).currentline) + + if fd then + res = fd:read() + fd:close() + res = cjson.decode(res) + if status == "01" then + res["onoff"] = "on" + else + res["onoff"] = "off" + end + res = cjson.encode(res) + local fd = io.open(TMP, "w") + fd:write(res) + fd:close() + end + require "MZLog".log(3, res) + + elseif types == "0d" then + local id = string.sub(value, 3, 4) + local led_light = string.sub(value, 5, 6) + local temp1 = string.sub(value, 7, 8) + local temp2 = string.sub(value, 9, 10) + led_light = string.format("%d", "0x" .. led_light) + local led_temp = temp2..temp1 + temp = string.format("%d", "0x" .. temp) + + local TMP = "/tmp/"..mac.."0" + local fd = io.open(TMP, "r") + local res = nil + require "MZLog".log(3, debug.getinfo(1).currentline) + + if fd then + res = fd:read() + fd:close() + res = cjson.decode(res) + res["led_light"] = led_light + res["led_temp"] = led_temp + + res = cjson.encode(res) + local fd = io.open(TMP, "w") + fd:write(res) + fd:close() + end + + elseif types == "0e" then + local id = string.sub(value, 3, 4) + local wait_time = string.format("%d", "0x" .. string.sub(value, 5, 6)) + --local mac = dbfs.getBluetoothDevice(id) + local TMP = "/tmp/0e0e" + local fd = io.open(TMP, "w") + fd:write(wait_time) + fd:close() + + elseif types == "10" then + local mac1 = string.sub(value, 3, 4) + local mac2 = string.sub(value, 5, 6) + local mac3 = string.sub(value, 7, 8) + local mac4 = string.sub(value, 9, 10) + local mac5 = string.sub(value, 11, 12) + local mac6 = string.sub(value, 13, 14) + local mac = mac6..mac5..mac4..mac3..mac2..mac1 + mac = string.upper(mac) + + local res = dbfs.fetchBluetoothDevice(mac) + if #res == 0 then + dbfs.addBluetoothDevice("", mac, "0123", "", "", "") + end + --[[ + if ret == nil then + dbfs.addBluetoothDevice("", mac, "", "", deviceType, "") + local TMP = "/tmp/"..mac + local fd = io.open(TMP, "w") + fd:write(os.time()) + fd:close() + else + local TMP = "/tmp/"..mac + local fd = io.open(TMP, "w") + ]] + end + require "MZLog".log(3, debug.getinfo(1).currentline) + require "MZLog".log(3, ret) + return ret +end + +function nw_get_bluetooth_info() + require "MZLog".log(3, debug.getinfo(1).currentline) + local res = bluetooth_info() + require "MZLog".log(3, debug.getinfo(1).currentline) + require "MZLog".log(3, res) + require "MZLog".log(3, debug.getinfo(1).currentline) + luci.http.write_json(res) +end + +function ww_get_bluetooth_info() + require "MZLog".log(3, debug.getinfo(1).currentline) + local res = bluetooth_info() + require "MZLog".log(3, debug.getinfo(1).currentline) + return res +end + +----------------------- scan_ble_device -------------------- +function scan_ble_switch(status) + local res = {} + if status == "on" then + local cmd = "bt_daemon -s ".."16".." 255" + lue(cmd) + require "MZLog".log(3, debug.getinfo(1).currentline) + require "MZLog".log(3, cmd) + posix.sleep(1) + res["result"] = true + elseif status == "off" then + local cmd = "bt_daemon -s ".."18".." 255" + lue(cmd) + posix.sleep(1) + res["result"] = false + end + return res +end + +function nw_scan_ble_switch() + local status = luci.http.formvalue("status") + require "MZLog".log(3, debug.getinfo(1).currentline) + require "MZLog".log(3, status) + local res = scan_ble_switch(status) + require "MZLog".log(3, res) + require "MZLog".log(3, debug.getinfo(1).currentline) + luci.http.write_json(res) +end + +function ww_scan_ble_switch(status) + local res = scan_ble_switch(status) + require "MZLog".log(3, res) + require "MZLog".log(3, debug.getinfo(1).currentline) + return cjson.encode(res) +end + +----------------------- get_ble_device_list -------------------- +function get_ble_device_list() + local res = dbfs.fetchAllBluetoothDevice() + require "MZLog".log(3, debug.getinfo(1).currentline) + require "MZLog".log(3, res) + local result = {} + if #res > 0 then + for k, v in pairs(res) do + local TMP = "/tmp/"..v.mac + local fd = io.open(TMP, "r") + if fd then + local time = fd:read() + fd:close() + if tonumber(os.time()) - tonumber(time) < 5 then + table.insert(result, v) + end + end + end + end + return result +end + +function nw_get_ble_device_list() + local res = get_ble_device_list() + require "MZLog".log(3, res) + require "MZLog".log(3, debug.getinfo(1).currentline) + if #res == 0 then + require "MZLog".log(3, debug.getinfo(1).currentline) + luci.http.write("[]") + else + luci.http.write_json(res) + end +end + +function ww_get_ble_device_list() + local res = get_ble_device_list() + require "MZLog".log(3, res) + require "MZLog".log(3, debug.getinfo(1).currentline) + if #res == 0 then + return "[]" + else + return cjson.encode(res) + end +end + +----------------------- add_ble_mesh_device -------------------- +function is_receive_id(mac) + local id = "" + local ret = dbfs.fetchBluetoothDevice(mac) + for k, v in pairs(ret) do + id = v.id + end + return id +end + +function add_ble_mesh_device(mac) + local res = {} + local id = "" + local mac1 = string.format("%d", "0x" .. string.sub(mac, 1, 2)) + local mac2 = string.format("%d", "0x" .. string.sub(mac, 3, 4)) + local mac3 = string.format("%d", "0x" .. string.sub(mac, 5, 6)) + local mac4 = string.format("%d", "0x" .. string.sub(mac, 7, 8)) + local mac5 = string.format("%d", "0x" .. string.sub(mac, 9, 10)) + local mac6 = string.format("%d", "0x" .. string.sub(mac, 11, 12)) + local macs = mac6.." "..mac5.." "..mac4.." "..mac3.." "..mac2.." "..mac1 + + require "MZLog".log(3, debug.getinfo(1).currentline) + require "MZLog".log(3, mac) + local cmd = "bt_daemon -s ".."17 "..macs + lue(cmd) + require "MZLog".log(3, debug.getinfo(1).currentline) + require "MZLog".log(3, cmd) + + for i = 1, 20 do + posix.sleep(1) + id = is_receive_id(mac) + if id ~= "" then + break + end + end + if id ~= "" then + res["result"] = true + res["id"] = id + res["mac"] = mac + else + res["result"] = false + res["mac"] = mac + end + return res +end + +function nw_add_ble_mesh_device() + local mac = luci.http.formvalue("mac") + local res = add_ble_mesh_device(mac) + require "MZLog".log(3, res) + require "MZLog".log(3, debug.getinfo(1).currentline) + luci.http.write_json(res) +end + +function ww_add_ble_mesh_device(mac) + local res = add_ble_mesh_device(mac) + require "MZLog".log(3, res) + require "MZLog".log(3, debug.getinfo(1).currentline) + return cjson.encode(res) +end + +----------------------- get_ble_device_detail -------------------- +function get_ble_device_status(mac) + local TMP1 = "/tmp/"..mac.."0" + local TMP2 = "/tmp/"..mac.."1" + local fd1 = io.open(TMP1, "r") + local fd2 = io.open(TMP2, "r") + if fd1 and fd2 then + local res1 = fd1:read() + local res2 = fd2:read() + fd1:close() + fd2:close() + --require "MZLog".log(3, res1) + --require "MZLog".log(3, res2) + if res1 ~= nil and res2 ~= nil then + res1 = cjson.decode(res1) + res2 = cjson.decode(res2) + res1["voltage"] = res2.voltage + res1["current"] = res2.current + res1["power"] = res2.power + res1["energy"] = res2.energy + + local ret = dbfs.fetchBluetoothDevice(mac) + local deviceType = nil + local name = nil + for k, v in pairs(ret) do + deviceType = v.deviceType + name = v.name + end + + res1["name"] = name + res1["type"] = deviceType + res1["time"] = nil + --require "MZLog".log(3, res1) + require "MZLog".log(3, debug.getinfo(1).currentline) + end + return res1 + else + return "{}" + end +end + +function nw_get_ble_device_status() + local mac = luci.http.formvalue("mac") + local res = get_ble_device_status(mac) + --require "MZLog".log(3, res) + require "MZLog".log(3, debug.getinfo(1).currentline) + if res == "{}" then + luci.http.write(res) + else + luci.http.write_json(res) + end +end + +function ww_get_ble_device_status(mac) + local res = get_ble_device_status(mac) + require "MZLog".log(3, res) + require "MZLog".log(3, debug.getinfo(1).currentline) + if res == "{}" then + return res + else + return cjson.encode(res) + end +end + +----------------------- remove_ble_from_mesh -------------------- +function is_remove_ble_from_mesh() + local res = nil + local TMP = "/tmp/0e0e" + local fd = io.open(TMP, "r") + if fd then + local ret = fd:read() + fd:close() + if ret ~= "" and ret ~= nil then + require "MZLog".log(3, debug.getinfo(1).currentline) + return ret + else + return nil + end + else + return nil + end +end + +function remove_ble_from_mesh(mac) + local res = {} + local ret = dbfs.fetchBluetoothDevice(mac) + local id = nil + for k, v in pairs(ret) do + id = v.id + end + if id ~= nil and id ~= "" then + local cmd = "bt_daemon -s ".."3 "..string.format("%d", "0x"..id) + lue(cmd) + local wait_time = nil + for i = 1, 20 do + posix.sleep(1) + wait_time = is_remove_ble_from_mesh(mac) + if wait_time ~= nil then + break + end + end + if wait_time then + res["result"] = true + res["waitTime"] = wait_time + res["mac"] = mac + res["id"] =id + else + res["result"] = false + res["mac"] = mac + res["id"] = id + end + else + res["result"] = false + res["mac"] = mac + end + return res +end + +function nw_remove_ble_from_mesh() + local mac = luci.http.formvalue("mac") + local res = remove_ble_from_mesh(mac) + require "MZLog".log(3, res) + require "MZLog".log(3, debug.getinfo(1).currentline) + luci.http.write_json(res) +end + +function ww_remove_ble_from_mesh(mac) + local res = remove_ble_from_mesh(mac) + require "MZLog".log(3, res) + require "MZLog".log(3, debug.getinfo(1).currentline) + return cjson.encode(res) +end + +----------------------- get_mesh_device_list -------------------- +function is_file_exist(TMP) + local fd = io.open(TMP, "r") + if fd then + return fd + else + return false + end +end + +function get_mesh_device_list() + local result = {} + dbfs.initBluetoothTable() + local ret = dbfs.fetchAllBleMeshDevice() + if #ret > 0 then + for k, v in pairs(ret) do + local res = {} + local TMP = "/tmp/" .. v.mac .."0" + local fd = nil + for i = 1, 10 do + fd = is_file_exist(TMP) + if fd then + break + else + posix.sleep(1) + end + end + if fd then + local value = fd:read() + if value ~= nil then + value = cjson.decode(value) + end + res["mac"] = v.mac + --res["online"] = true + res["name"] = v.name + res["type"] = v.deviceType + if value["onoff"] == "on" then + res["onoff"] = "on" + else + res["onoff"] = "off" + end + + if tonumber(os.time()) - tonumber(value.time) > 60 then + res["online"] = false + else + res["online"] = true + end + + if res["online"] == false then + res = nil + end + table.insert(result, res) + require "MZLog".log(3, debug.getinfo(1).currentline) + else + res["mac"] = v.mac + res["name"] = v.name + res["type"] = v.deviceType + res["onoff"] = "off" + res["online"] = false + table.insert(result, res) + require "MZLog".log (3, debug.getinfo(1).currentline) + end + end + end + return result +end + +function nw_get_mesh_device_list() + local res = get_mesh_device_list() + --require "MZLog".log(3, res) + require "MZLog".log(3, debug.getinfo(1).currentline) + if #res == 0 then + luci.http.write("[]") + else + luci.http.write_json(res) + end +end + +function ww_get_mesh_device_list() + local res = get_mesh_device_list() + require "MZLog".log(3, res) + require "MZLog".log(3, debug.getinfo(1).currentline) + if #res == 0 then + return "[]" + else + return cjson.encode(res) + end +end + +----------------------- dismiss_mesh -------------------- +function dismiss_mesh() + local res = {} + local cmd = "bt_daemon -s ".."3 ".." 255" + lue(cmd) + res["result"] = true +end +function nw_dismiss_mesh() + local res = dismiss_mesh() + require "MZLog".log(3, res) + require "MZLog".log(3, debug.getinfo(1).currentline) + luci.http.write_json(res) +end + +----------------------- set_mesh_device_attr -------------------- +function is_switch_on(mac) + local TMP = "/tmp/"..mac.."0" + local fd = io.open(TMP, "r") + if fd then + local res = fd:read() + fd:close() + res = cjson.decode(res) + if res["onoff"] == "on" then + return true + else + return nil + end + end +end + +function is_switch_off(mac) + local TMP = "/tmp/"..mac.."0" + local fd = io.open(TMP, "r") + if fd then + local res = fd:read() + fd:close() + res = cjson.decode(res) + if res["onoff"] == "off" then + return true + else + return nil + end + end +end + +function is_set_name_ok(mac) + local name = "" + local ret = dbfs.fetchBluetoothDevice(mac) + for k, v in pairs(ret) do + name = v.name + end + return name +end + +function set_mesh_device_attr(mac, key, value) + local res = {} + require "MZLog".log(3, debug.getinfo(1).currentline) + require "MZLog".log(3, mac) + require "MZLog".log(3, key) + require "MZLog".log(3, value) + local ret = dbfs.fetchBluetoothDevice(mac) + local id = nil + if #ret > 0 then + for k, v in pairs(ret) do + id = v.id + end + end + + if id ~= nil then + if key == "8" and value == "true" then + local cmd = "bt_daemon -s ".."1 "..string.format("%d", "0x"..id).." 1" + lue(cmd) + local flag = nil + for i = 1, 10 do + flag = is_switch_on(mac) + if flag then + break + else + posix.sleep(1) + end + end + if flag then + res["result"] = true + res["mac"] = mac + res["key"] = key + res["onoff"] = " on" + else + res["result"] = false + res["mac"] = mac + res["key"] = key + res["onoff"] = "off" + end + + elseif key == "8" and value == "false" then + local cmd = "bt_daemon -s ".."1 "..string.format("%d", "0x"..id).." 0" + lue(cmd) + local flag = nil + for i = 1, 10 do + flag = is_switch_off(mac) + if flag then + break + else + posix.sleep(1) + end + end + if flag then + res["result"] = true + res["mac"] = mac + res["key"] = key + res[ "onoff"] = "off" + else + res[ "result"] = false + res["mac"] = mac + res["key"] = key + res["onoff"] = "on" + end + + elseif key == "0" then + require "MZLog".log(3, debug.getinfo(1).currentline) + require "MZLog".log(3, value) + if #value > 20 then + value = string.sub(value, 1, 20) + end + require "MZLog".log(3, debug.getinfo(1).currentline) + local name = "" + for i = 1, #value, 2 do + name = name.." "..string.format("%d", "0x"..string.sub(value, i, i+1)) + end + + require "MZLog".log(3, debug.getinfo(1).currentline) + require "MZLog".log(3, name) + local len = #value + if len > 20 then + len = 20 + end + require "MZLog".log(3, len) + dbfs.updateBluetoothDevice(id, "", "", len, mac) + require "MZLog".log(3, debug.getinfo(1).currentline) + local cmd = "bt_daemon -s ".."13 "..string.format("%d", "0x"..id).." "..name + lue(cmd) + require "MZLog".log(3, debug.getinfo(1).currentline) + require "MZLog".log(3, cmd) + + local name = "" + for i = 1, 10 do + posix.sleep(1) + name = is_set_name_ok(mac) + if name ~= "" then + break + end + end + + if name ~= "" and name ~= nil then + res ["result"] = true + res["mac"] = mac + res["key"] = key + else + res[ "result"] = false + res["mac"] = mac + res["key"] = key + end + require "MZLog".log(3, debug.getinfo(1).currentline) + require "MZLog".log(3, debug.getinfo(1).currentline) + end + else + res["result"] = false + res["mac"] = mac + res["key"] = key + end + return res +end + +function nw_set_mesh_device_attr() + local mac = luci.http.formvalue("mac") + local key = luci.http.formvalue("key") + local value = luci.http.formvalue("value") + local res = set_mesh_device_attr(mac, key, value) + require "MZLog".log(3, res) + require "MZLog".log(3, debug.getinfo(1).currentline) + luci.http.write_json(res) +end + +function ww_set_mesh_device_attr(mac, key, value) + local res = set_mesh_device_attr(mac, key, value) + require "MZLog".log(3, res) + require "MZLog".log(3, debug.getinfo(1).currentline) + return cjson.encode(res) +end + +----------------------- reboot_mesh_device -------------------- +function reboot_mesh_device(mac) + local res = {} + local ret = dbfs.fetchBluetoothDevice(mac) + local id = nil + for k, v in pairs(ret) do + id = v.id + end + + local cmd = "bt_daemon -s ".."4 "..string.format("%d", "0x"..id) + lue(cmd) + posix.sleep(2) + res["result"] = true +end + +function nw_reboot_mesh_device() + local mac = luci.http.formvalue("mac") + local res = reboot_mesh_device(mac) + require "MZLog".log(3, res) + require "MZLog".log(3, debug.getinfo(1).currentline) + luci.http.write_json(res) +end + +function ww_reboot_mesh_device(mac) + local res = reboot_mesh_device(mac) + require "MZLog".log(3, res) + require "MZLog".log(3, debug.getinfo(1).currentline) + return cjson.encode(res) +end + +----------------------- unmesh_all_device -------------------- +function unmesh_all_device() + local res = {} + local cmd = "bt_daemon -s ".."3 ".."255" + lue(cmd) + require "MZLog".log(3, debug.getinfo(1).currentline) + local wait_time = nil + for i = 1, 20 do + wait_time = is_remove_ble_from_mesh() + require "MZLog".log(3, debug.getinfo(1).currentline) + if wait_time ~= nil then + break + else + posix.sleep(1) + end + end + if wait_time then + require "MZLog".log(3, debug.getinfo(1).currentline) + posix.sleep(5) + posix.sleep(5) + posix.sleep(5) + --posix.sleep(tonumber(wait_time)) + require "MZLog".log(3, debug.getinfo(1).currentline) + res["result"] = true + else + res["result"] = false + end + require "MZLog".log(3, debug.getinfo(1).currentline) + return res +end + +function nw_unmesh_all_device() + local res = unmesh_all_device() + require "MZLog".log(3, res) + require "MZLog".log(3, debug.getinfo(1).currentline) + luci.http.write_json(res) +end + +function ww_unmesh_all_device() + local res = unmesh_all_device() + require "MZLog".log(3, res) + require "MZLog".log(3, debug.getinfo(1).currentline) + return cjson.encode(res) +end + +----------------------- set_mesh_device_timer -------------------- +function is_set_timer_ok(mac, timer_id) + local TMP = "/tmp/"..mac.."0" + local fd = io.open(TMP, "r") + if fd then + local ret = fd:read() + ret = cjson.decode(ret) + fd:close() + if ret["timerId"] == timer_id then + return true + else + return nil + end + else + return nil + end +end +function set_mesh_device_timer(mac, timer_id, flag, start_time, end_time) + local res = {} + local ret = dbfs.fetchBluetoothDevice(mac) + local id = nil + for k ,v in pairs(ret) do + id = v.id + end + local ret = dbfs.getBleTimerId(id, timer_id) + if ret == "" then + dbfs.addBleTimer(id, timer_id, flag, start_time, end_time) + end + --[[ + local start = start + local ends = ends + if string.len(start) == 6 then + start = "00"..start + elseif string.len(start) == 5 then + start = "000"..start + elseif string.len(start) == 4 then + start = "0000"..start + end + + if string.len(ends) == 6 then + ends = "00"..ends + elseif string.len(ends) == 5 then + ends = "000"..ends + elseif string.len(ends) == 4 then + ends = "0000"..ends + end + require "MZLog".log(3, mac) + require "MZLog".log(3, timer_id) + require "MZLog".log(3, flag) + require "MZLog".log(3, start) + require "MZLog".log(3, ends) + + local TMP = "/tmp/"..mac.."1" + local fd = io.open(TMP, "r") + if fd then + local res = fd:read() + res = cjson.decode(res) + res["timerId"] = timer_id + res = cjson.encode(res) + local fd = io.open(TMP, "w") + fd:write(res) + fd:close() + end + + local start1 = string.sub(start, 1, 2) + local start2 = string.sub(start, 3, 4) + local start3 = string.sub(start, 5, 6) + local start4 = string.sub(start, 7, 8) + local end1 = string.sub(ends, 1, 2) + local end2 = string.sub(ends, 3, 4) + local end3 = string.sub(ends, 5, 6) + local end4 = string.sub(ends, 7, 8) + if id then + local start = string.format("%d", "0x"..start1).." ".. + string.format("%d", "0x"..start2).." ".. + string.format("%d", "0x"..start3).." ".. + string.format("%d", "0x"..start4) + local ends = string.format("%d", "0x"..end1).." ".. + string.format("%d", "0x"..end2).." ".. + string.format("%d", "0x"..end3).." ".. + string.format("%d", "0x"..end4) + + local cmd = "/root/spi_send ".."6 "..string.format("%d", "0x"..id).. + " "..string.format("%d", "0x"..flag).." ".. + string.format("%d", "0x"..timer_id).." "..start.." "..ends + require "MZLog".log(3, cmd) + lue(cmd) + + local times = tonumber(os.time()) - 1420041600 + local res = string.format("%x", times) + local time = "" + for i = 1, #res, 2 do + time = time.." "..string.format("%d", "0x"..string.sub(res, i, i+1)) + end + local cmd = "/root/spi_send ".."7 "..string.format("%d", "0x"..id).." "..time + lue(cmd) + end + + local flag = nil + for i = 1, 10 do + posix.sleep(1) + flag = is_set_timer_ok(mac, timer_id) + if flag then + break + end + end + if flag then + res["result"] = true + res["mac"] = mac + res["timerId"] = timer_id + else + res["result"] = false + res["mac"] = mac + res["timerId"] = timer_id + end + return res + ]] +end + +function nw_set_mesh_device_timer() + local mac = luci.http.formvalue("mac") + local timer_id = luci.http.formvalue("timerId") + local flag = luci.http.formvalue("flag") + local start_time = luci.http.formvalue("start") + local end_time = luci.http.formvalue("ends") + local timer = luci.http.formvalue("timer") + local res = set_mesh_device_timer(mac, timer_id, flag, start_time, end_time) + require "MZLog".log(3, res) + require "MZLog".log(3, debug.getinfo(1).currentline) + luci.http.write_json(res) +end + +function ww_set_mesh_device_timer(mac, timer_id, flag, start, ends) + local res = set_mesh_device_timer(mac, timer_id, flag, start, ends) + require "MZLog".log(3, res) + require "MZLog".log(3, debug.getinfo(1).currentline) + return cjson.encode(res) +end + +----------------------- del_mesh_device_timer -------------------- +function is_del_timer_ok(mac, timer_id) + local TMP = "/tmp/"..mac.."0" + local fd = io.open(TMP, r) + if fd then + local ret = fd:read() + ret = cjson.decode(ret) + fd:close() + if ret["timerId"] ~= timer_id then + return true + else + return nil + end + else + return nil + end +end + +function del_mesh_device_timer(mac, timer_id) + local res = {} + local ret = dbfs.fetchBluetoothDevice(mac) + local id = nil + for k ,v in pairs(ret) do + id = v.id + end + local ret = dbfs.getBleTimerId(id, timer_id) + if ret ~= "" then + dbfs.deleteBleTimer(id, timer_id) + end + + --[[ + local cmd = "/root/spi_send ".."10 "..string.format("%d", "0x"..id).. + " "..string.format("%d", "0x"..timer_id) + lue(cmd) + local flag = nil + for i = 1, 10 do + posix.sleep(1) + flag = is_del_timer_ok(mac, timer_id) + if flag then + break + end + end + if flag then + res["result"] = true + res["mac"] = mac + res["timerId"] = timer_id + else + res["result"] = false + res["mac"] = mac + res["timerId"] = timer_id + end + return res + ]] +end + +function nw_del_mesh_device_timer() + local mac = luci.http.formvalue("mac") + local timer_id = luci.http.formvalue("timerId") + local res = del_mesh_device_timer(mac, timer_id) + require "MZLog".log(3, res) + require "MZLog".log(3, debug.getinfo(1).currentline) + luci.http.write_json(res) +end + +function ww_del_mesh_device_timer(mac, timer_id) + local res = del_mesh_device_timer(mac, timer_id) + require "MZLog".log(3, res) + require "MZLog".log(3, debug.getinfo(1).currentline) + return cjson.encode(res) +end + +----------------------- set_encry_info -------------------- +function is_set_key_ok() + local TMP = "/tmp/0b0b" + local fd = io.open(TMP, "r") + require "MZLog".log(3, debug.getinfo(1).currentline) + if fd then + local file = fd:read() + require "MZLog".log(3, debug.getinfo(1).currentline) + require "MZLog".log(3, file) + fd:close() + if file == "00" then + return true + elseif file == "01" then + require "MZLog".log(3, debug.getinfo(1).currentline) + return false + end + end +end + +function get_ble_device_key() + local ret = dbfs.fetchBluetoothDeviceKey() + if #ret > 0 then + return ret + else + return nil + end +end + +function set_mesh_network_pwd(old_key, new_key) + --local TMP = "/tmp/"..new_key + --local fd = io.open(TMP, "w") + --fd:write(new_key) + --fd:close() + require "MZLog".log(3, debug.getinfo(1).currentline) + --"0123" + --"8888" + require "MZLog".log(3, old_key) + local key = new_key + require "MZLog".log(3, new_key) + local cmd = "bt_daemon -s 21" + lue(cmd) + local ret = nil + for i = 1, 10 do + ret = get_ble_device_key() + if ret then + break + else + posix.sleep(1) + end + end + + if #ret > 0 then + for k, v in pairs(ret) do + if v.key ~= old_key then + old_key = v.key + end + end + end + + local res = {} + require "MZLog".log(3, old_key) + require "MZLog".log(3, new_key) + local old_key1 = string.sub(old_key, 1, 1) + local old_key2 = string.sub(old_key, 2, 2) + local old_key3 = string.sub(old_key, 3, 3) + local old_key4 = string.sub(old_key, 4, 4) + local new_key1 = string.sub(new_key, 1, 1) + local new_key2 = string.sub(new_key, 2, 2) + local new_key3 = string.sub(new_key, 3, 3) + local new_key4 = string.sub(new_key, 4, 4) + require "MZLog".log(3, old_key1) + require "MZLog".log(3, old_key2) + require "MZLog".log(3, old_key3) + require "MZLog".log(3, old_key4) + require "MZLog".log(3, debug.getinfo(1).currentline) + require "MZLog".log(3, debug.getinfo(1).currentline) + require "MZLog".log(3, debug.getinfo(1).currentline) + require "MZLog".log(3, debug.getinfo(1).currentline) + + --bt_daemon -s 21 + + local old_key = old_key1.." "..old_key2.." "..old_key3.." "..old_key4 + local new_key = new_key1.." "..new_key2.." "..new_key3.." "..new_key4 + local cmd = "bt_daemon -s ".."9 "..old_key.." "..new_key + lue(cmd) + require "MZLog".log(3, cmd) + local flag = nil + for i = 1, 10 do + flag = is_set_key_ok() + if flag ~= nil then + break + else + posix.sleep(1) + end + end + require "MZLog".log(3, debug.getinfo(1).currentline) + require "MZLog".log(3, ret) + if #ret > 0 then + for k, v in pairs(ret) do + local mac = v.mac + dbfs.updateBluetoothDevice("", key, "", "", mac) + end + end + + if flag then + res["result"] = true + res["newKey"] = key + else + res["result"] = false + res["newKey"] = key + end + return res +end + +function nw_set_mesh_network_pwd() + local old_key = luci.http.formvalue("oldKey") + local new_key = luci.http.formvalue("newKey") + local res = set_mesh_network_pwd(old_key, new_key) + require "MZLog".log(3, res) + require "MZLog".log(3, debug.getinfo(1).currentline) + luci.http.write_json(res) +end + +function ww_set_mesh_network_pwd (old_key, new_key) + local res = set_mesh_network_pwd(old_key, new_key) + require "MZLog".log(3, res) + require "MZLog".log(3, debug.getinfo(1).currentline) + return cjson.encode(res) +end + +function set_lamp_brightness() + +end + +function nw_set_lamp_brightness() + local res = set_lamp_brightness() + require "MZLog".log(3, res) + require "MZLog".log(3, debug.getinfo(1).currentline) + luci.http.write_json(res) +end + +function ww_set_lamp_brightness() + local res = set_lamp_brightness() + require "MZLog".log(3, res) + require "MZLog".log(3, debug.getinfo(1).currentline) + luci.http.write_json(res) +end + +------------------------ bluetooth -------------------- diff --git a/Me_Lua/r13/meizu/r10config.lua b/Me_Lua/r13/meizu/r10config.lua new file mode 100644 index 0000000..e3fdb3c --- /dev/null +++ b/Me_Lua/r13/meizu/r10config.lua @@ -0,0 +1,3 @@ +upgradeFlagFile = "/tmp/upgradeFlagFile" +upgradeLOCK = "/bin/touch "..upgradeFlagFile +upgradeUNLOCK = "/bin/rm "..upgradeFlagFile diff --git a/Me_Lua/r13/meizu/sipfs.lua b/Me_Lua/r13/meizu/sipfs.lua new file mode 100644 index 0000000..27ab6c5 --- /dev/null +++ b/Me_Lua/r13/meizu/sipfs.lua @@ -0,0 +1,690 @@ +module("meizu.sipfs", package.seeall) +--sip functions + +local bfs = require "meizu.bfs" +local cjson = require "cjson" +local nwfs = require "meizu.nwfs" +local RC = require "meizu.r10config" +local sipfs = require "meizu.sipfs" +local upgdfs = require "meizu.upgdfs" + +local b64dec = bfs.b64dec +local bind_router = bfs.bind_router +local data_to_json = bfs.data_to_json +local exec_cmd_in_sh = bfs.exec_cmd_in_sh +local exec_reboot = bfs.exec_reboot +local get_device_SN = bfs.get_device_SN +local get_device_version = bfs.get_device_version +local get_https_data = bfs.get_https_data +local factory_reset = bfs.factory_reset +local rts_get_access_token = bfs.rts_get_access_token +local set_passwd = bfs.set_passwd +local silent_upgrade = bfs.silent_upgrade + +local real_time_net_speed = nwfs.real_time_net_speed +local set_device_name = nwfs.set_device_name +local set_wan_switch = nwfs.set_wan_switch +local wifi_settings = nwfs.wifi_settings +local ww_get_connect_device_list = nwfs.ww_get_connect_device_list +local ww_get_device_details = nwfs.ww_get_device_details +local ww_get_wifi_settings = nwfs.ww_get_wifi_settings +local ww_get_wireless_channel = nwfs.ww_get_wireless_channel +local ww_set_wireless_channel = nwfs.ww_set_wireless_channel + +local ww_scan_ble_switch = nwfs.ww_scan_ble_switch +local ww_add_ble_mesh_device = nwfs.ww_add_ble_mesh_device +local ww_get_ble_device_list = nwfs.ww_get_ble_device_list +local ww_get_ble_device_status = nwfs.ww_get_ble_device_status +local ww_set_mesh_device_attr = nwfs.ww_set_mesh_device_attr +local ww_get_mesh_device_list = nwfs.ww_get_mesh_device_list +local ww_reboot_mesh_device = nwfs.ww_reboot_mesh_device +local ww_remove_ble_from_mesh = nwfs.ww_remove_ble_from_mesh +local ww_unmesh_all_device = nwfs.ww_unmesh_all_device +local ww_set_mesh_device_timer = nwfs.ww_set_mesh_device_timer +local ww_del_mesh_device_timer = nwfs.ww_del_mesh_device_timer + +local check_upgrade = upgdfs.check_upgrade +local do_upgrade = upgdfs.do_upgrade +local local_upgrade = upgdfs.local_upgrade + +local table_merge = bfs.table_merge + +function sip_get_parameters(commandId) + local url = "https://router.meizu.com/oauth/router/command/routerRequest?" + local https = require("ssl.https") + local access_token = rts_get_access_token() + local newurl = url.."access_token="..access_token + newurl = newurl.."&commandId="..commandId + require "MZLog".log(3, debug.getinfo(1).currentline) + require "MZLog".log(3, url) + require "MZLog".log(3, debug.getinfo(1).currentline) + local res, code, headers, status = https.request(newurl) + require "MZLog".log(3, debug.getinfo(1).currentline) + require "MZLog".log(3, code) + require "MZLog".log(3, status) + require "MZLog".log(3, res) + require "MZLog".log(3, debug.getinfo(1).currentline) + if code == 401 then + delete_access_token() + access_token = rts_get_access_token() + local newurl = url.."access_token="..access_token + newurl = newurl.."&commandId="..commandId + res, code, headers, status = https.request(newurl) + end + + return res +end + +function sip_response_uploader(cmd, commandId, data, finishstatus) + require "MZLog".log(3, debug.getinfo(1).currentline) + require "MZLog".log(3, data) + if data == nil or data == "" then + return + end + require "MZLog".log(3, debug.getinfo(1).currentline) + local url="https://router.meizu.com/oauth/router/command/updateResponse" + local https = require("ssl.https") + local timemini = os.date("%s") + local access_token = rts_get_access_token() + local pd = init_update_resp_pd(access_token, commandId, data, finishstatus, timemini) + + require "MZLog".log(3, debug.getinfo(1).currentline) + require "MZLog".log(3, url) + require "MZLog".log(3, pd) + require "MZLog".log(3, debug.getinfo(1).currentline) + local res, code, headers, status = https.request(url, pd) + require "MZLog".log(3, debug.getinfo(1).currentline) + require "MZLog".log(3, code) + require "MZLog".log(3, status) + require "MZLog".log(3, res) + require "MZLog".log(3, debug.getinfo(1).currentline) + if code == 401 then + delete_access_token() + access_token = rts_get_access_token() + pd = init_update_resp_pd(access_token, commandId, data, finishstatus, timemini) + res, code, headers, status = https.request(url, pd) + end + return res +end + +function init_update_resp_pd(access_token, commandId, data, finishstatus, timemini) + local pd = "access_token="..access_token + pd = pd.."&commandId="..commandId + pd = pd.."&commandResponse="..(data or "") + local status = 2 + if finishstatus then + status = finishstatus + end + pd = pd.."&status="..status + pd = pd.."&lastExcuteTime="..timemini + return pd +end + +function download_list_post_process(data, refine_cnt) + local new_data = {} + local jsd = cjson.decode(data) + local nd_msg = {} + if type(jsd) == "table" then + local msg = "" + for k, v in pairs(jsd) do + if k and k == "message" then + msg = v + else + new_data[k] = v + end + end + if type(msg) == "table" and _G.next(msg) ~= nil then + local cnt = 0 + for k, v in pairs(msg) do + if cnt < refine_cnt then + table.insert(nd_msg, v) + cnt = cnt + 1 + end + end + end + if _G.next(nd_msg) ~= nil then + new_data["message"] = nd_msg + else + new_data["message"] = "[]" + end + end + + return nd_msg +end + +function download_task_operate_process(cmd, cmdid) + local data = sip_get_parameters(cmdid) + local jsr = cjson.decode(data) + local value = jsr.value + local ret = "" + if (jsr.code) == "200" then + for k, v in pairs(value) do + if k == "commandRequest" then + local jsr = cjson.decode(v) + local gid = jsr.gid + ret = download_task_operate(gid, cmd) + end + end + end + sip_response_uploader(cmd, cmdid, ret) +end + +function ww_exec_reboot(cmd, cmdid) + local ret = {} + ret["result"] = true + sip_response_uploader(cmd, cmdid, data_to_json(ret)) + exec_reboot() +end + +sip_cmd_process_action = { + ["realtimenetspeed"] = function(cmd, cmdid) + local data = data_to_json(real_time_net_speed()) + sip_response_uploader(cmd, cmdid, data, 2) + end, + ["factoryreset"] = function(cmd, cmdid) + local data = '{'..'status:"reset factory ok."'..'}' + local data = factory_reset() + sip_response_uploader(cmd, cmdid, data_to_json(data)) + end, + ["getDeviceList"] = function(cmd, cmdid) + require "MZLog".log(3, debug.getinfo(1).currentline) + local data = ww_get_connect_device_list() + data = data_to_json(data) + require "MZLog".log(3, debug.getinfo(1).currentline) + require "MZLog".log(3, data) + require "MZLog".log(3, debug.getinfo(1).currentline) + sip_response_uploader(cmd, cmdid, data) + end, + ["getwifisettings"] = function(cmd, cmdid) + require "MZLog".log(3, debug.getinfo(1).currentline) + local data = sip_get_parameters(cmdid) + require "MZLog".log(3, data) + local app_version = nil + local jsr = cjson.decode(data) + local value = jsr.value + for k, v in pairs(value) do + if k == "commandRequest" then + if #v > 1 then + local value = cjson.decode(v) + app_version = value.appVer + end + end + end + + if app_version == nil then + app_version = 0 + end + require "MZLog".log(3, app_version) + require "MZLog".log(3, debug.getinfo(1).currentline) + local data = ww_get_wifi_settings(app_version) + require "MZLog".log(3, debug.getinfo(1).currentline) + require "MZLog".log(3, data) + require "MZLog".log(3, debug.getinfo(1).currentline) + sip_response_uploader(cmd, cmdid, data) + end, + ["setwifisettings"] = function(cmd, cmdid) + require "MZLog".log(3, debug.getinfo(1).currentline) + local data = sip_get_parameters(cmdid) + local jsr = cjson.decode(data) + require "MZLog".log(3, debug.getinfo(1).currentline) + + local value = jsr.value + local switch_2g = nil + local switch_5g = nil + local ssid_2g = nil + local ssid_5g = nil + local pwd_2g = nil + local pwd_5g = nil + local encry_2g = nil + local encry_5g = nil + + for k, v in pairs(value) do + if k == "commandRequest" then + require "MZLog".log(3, debug.getinfo(1).currentline) + require "MZLog".log(3, v) + + require "MZLog".log(3, debug.getinfo(1).currentline) + local value = cjson.decode(v) + require "MZLog".log(3, value) + local base64 = value.base64 + require "MZLog".log(3, debug.getinfo(1).currentline) + require "MZLog".log(3, base64) + local app_version = value.appVer + if app_version == nil then + app_version = 0 + end + require "MZLog".log(3, app_version) + + if tonumber(app_version) >= 5 then + require "MZLog".log(3, debug.getinfo(1).currentline) + require "MZLog".log(3, base64) + v = b64dec(base64) + require "MZLog".log(3, debug.getinfo(1).currentline) + require "MZLog".log(3, v) + local jsr = cjson.decode(v) + for key, value in pairs(jsr) do + if value.name == "wl0" then + switch_2g = value.on + ssid_2g = value.ssid + pwd_2g = value.pwd + encry_2g = value.encryption + elseif value.name == "wl1" then + switch_5g = value.on + ssid_5g = value.ssid + pwd_5g = value.pwd + encry_5g = value.encryption + end + end + else + local value = cjson.decode(v) + require "MZLog".log(3, debug.getinfo(1).currentline) + require "MZLog".log(3, debug.getinfo(1).currentline) + require "MZLog".log(3, value) + switch_2g = value.on1 + ssid_2g = value.ssid1 + pwd_2g = value.pwd1 + encry_2g = value.encryption1 + switch_5g = value.on2 + ssid_5g = value.ssid2 + pwd_5g = value.pwd2 + encry_5g = value.encryption2 + end + end + end + local data = wifi_settings(switch_2g, ssid_2g, pwd_2g, encry_2g, switch_5g, ssid_5g, pwd_5g, encry_5g) + + require "MZLog".log(3, debug.getinfo(1).currentline) + data = cjson.encode(data) + require "MZLog".log(3, data) + sip_response_uploader(cmd, cmdid, data) + end, + ["checkRouterUpdate"] = function(cmd, cmdid) + require "MZLog".log(3, debug.getinfo(1).currentline) + local data = check_upgrade() + require "MZLog".log(3, data) + sip_response_uploader(cmd, cmdid, data) + end, + ["executeRouterUpdate"] = function(cmd, cmdid) + require "MZLog".log(3, debug.getinfo(1).currentline) + local data = do_upgrade() + if data ~= "" then + require "MZLog".log(3, data) + sip_response_uploader(cmd, cmdid, data) + end + end, + ["setDeviceName"] = function(cmd, cmdid) + local data = sip_get_parameters(cmdid) + local jsr = cjson.decode(data) + local value = jsr.value + local mac = nil + local devicename = nil + + for k, v in pairs(value) do + if k == "commandRequest" then + local jsr = cjson.decode(v) + mac = jsr.mac + devicename = jsr.devicename + end + end + + require "MZLog".log(3, debug.getinfo(1).currentline) + local data = set_device_name(mac, devicename) + require "MZLog".log(3, data) + sip_response_uploader(cmd, cmdid, data) + end, + ["setWanSwitch"] = function(cmd, cmdid) + local data = sip_get_parameters(cmdid) + local jsr = cjson.decode(data) + local mac = nil + local mode = nil + local enable = nil + local value = jsr.value + for k, v in pairs(value) do + if k == "commandRequest" then + local jsr = cjson.decode(v) + mac = jsr.mac + mode = jsr.mode + enable = jsr.enable + end + end + local data = set_wan_switch(mac, mode, enable) + data = data_to_json(data) + require "MZLog".log(3, data) + sip_response_uploader(cmd, cmdid, data) + end, + ["setReboot"] = function(cmd, cmdid) + require "MZLog".log(3, debug.getinfo(1).currentline) + ww_exec_reboot(cmd, cmdid) + end, + ["getdevicedetails"] = function(cmd, cmdid) + local data = sip_get_parameters(cmdid) + local jsr = cjson.decode(data) + local value = jsr.value + local ret = "" + if (jsr.code) == "200" then + for k, v in pairs(value) do + if k == "commandRequest" then + local jsr = cjson.decode(v) + local mac = jsr.mac + ret = data_to_json(ww_get_device_details(mac)) + end + end + end + sip_response_uploader(cmd, cmdid, ret) + end, + ["getsysinfo"] = function(cmd, cmdid) + local data = require "meizu.bfs".sysinfo() + data = data_to_json(data) + sip_response_uploader(cmd, cmdid, data) + end, + ["getWirelessChannel"] = function(cmd, cmdid) + local ret = ww_get_wireless_channel() + sip_response_uploader(cmd, cmdid, ret) + end, + ["setWirelessChannel"] = function(cmd, cmdid) + local data = sip_get_parameters(cmdid) + local jsr = cjson.decode(data) + local value = jsr.value + local ret = "" + if (jsr.code) == "200" then + for k, v in pairs(value) do + if k == "commandRequest" then + local jsr = cjson.decode(v) + local channel = jsr.channel + ret = data_to_json(ww_set_wireless_channel(channel)) + end + end + end + sip_response_uploader(cmd, cmdid, ret) + end, + ["scanBleSwitch"] = function(cmd, cmdid) --scanBleSwitch getMeshDeviceList getBleDeviceList + local data = sip_get_parameters(cmdid) + local jsr = cjson.decode(data) + local value = jsr.value + local ret = "" + if (jsr.code) == "200" then + for k, v in pairs(value) do + if k == "commandRequest" then + local jsr = cjson.decode(v) + local status = jsr.status + ret = ww_scan_ble_switch(status) + end + end + end + require "MZLog".log(3, ret) + require "MZLog".log(3, debug.getinfo(1).currentline) + sip_response_uploader(cmd, cmdid, ret) + end, + ["getBleDeviceList"] = function(cmd, cmdid) + local data = ww_get_ble_device_list() + require "MZLog".log(3, data) + require "MZLog".log(3, debug.getinfo(1).currentline) + sip_response_uploader(cmd, cmdid, data) + end, + ["addMeshDevice"] = function(cmd, cmdid) + local data = sip_get_parameters(cmdid) + local jsr = cjson.decode(data) + local value = jsr.value + local ret = "" + if (jsr.code) == "200" then + for k, v in pairs(value) do + if k == "commandRequest" then + local jsr = cjson.decode(v) + local mac = jsr.mac + ret = ww_add_ble_mesh_device(mac) + end + end + end + require "MZLog".log(3, ret) + require "MZLog".log(3, debug.getinfo(1).currentline) + sip_response_uploader(cmd, cmdid, ret) + end, + ["getMeshDeviceDetail"] = function(cmd, cmdid) + require "MZLog".log(3, debug.getinfo(1).currentline) + local data = sip_get_parameters(cmdid) + local jsr = cjson.decode(data) + local value = jsr.value + local ret = "" + require "MZLog".log(3, debug.getinfo(1).currentline) + require "MZLog".log(3, value) + if (jsr.code) == "200" then + for k, v in pairs(value) do + if k == "commandRequest" then + local jsr = cjson.decode(v) + local mac = jsr.mac + require "MZLog".log(3, debug.getinfo(1).currentline) + require "MZLog".log(3, mac) + require "MZLog".log(3, debug.getinfo(1).currentline) + ret = ww_get_ble_device_status(mac) + end + end + end + require "MZLog".log(3, ret) + require "MZLog".log(3, debug.getinfo(1).currentline) + sip_response_uploader(cmd, cmdid, ret) + end, + ["removeblefrommesh"] = function(cmd, cmdid) + local data = sip_get_parameters(cmdid) + local jsr = cjson.decode(data) + local value = jsr.value + local ret = "" + if (jsr.code) == "200" then + for k, v in pairs(value) do + if k == "commandRequest" then + local jsr = cjson.decode(v) + local mac = jsr.mac + ret = ww_remove_ble_from_mesh(mac) + end + end + end + require "MZLog".log(3, ret) + require "MZLog".log(3, debug.getinfo(1).currentline) + sip_response_uploader(cmd, cmdid, ret) + end, + ["getMeshDeviceList"] = function(cmd, cmdid) + local data = ww_get_mesh_device_list() + require "MZLog".log(3, data) + require "MZLog".log(3, debug.getinfo(1).currentline) + sip_response_uploader(cmd, cmdid, data) + end, + ["setMeshDeviceAttr"] = function(cmd, cmdid) + local data = sip_get_parameters(cmdid) + local jsr = cjson.decode(data) + local value = jsr.value + local ret = "" + if (jsr.code) == "200" then + for k, v in pairs(value) do + if k == "commandRequest" then + local jsr = cjson.decode(v) + local mac = jsr.mac + local key = jsr.key + local value = jsr.value + ret = ww_set_mesh_device_attr(mac, key, value) + end + end + end + require "MZLog".log(3, ret) + require "MZLog".log(3, debug.getinfo(1).currentline) + sip_response_uploader(cmd, cmdid, ret) + end, + ["rebootmeshdevice"] = function(cmd, cmdid) + local data = sip_get_parameters(cmdid) + local jsr = cjson.decode(data) + local value = jsr.value + local ret = "" + if (jsr.code) == "200" then + for k, v in pairs(value) do + if k == "commandRequest" then + local jsr = cjson.decode(v) + local mac = jsr.mac + ret = ww_reboot_mesh_device(mac) + end + end + end + require "MZLog".log(3, ret) + require "MZLog".log(3, debug.getinfo(1).currentline) + sip_response_uploader(cmd, cmdid, ret) + end, + ["unmeshalldevice"] = function(cmd, cmdid) + local data = ww_unmesh_all_device() + require "MZLog".log(3, data) + require "MZLog".log(3, debug.getinfo(1).currentline) + sip_response_uploader(cmd, cmdid, data) + end, + ["setmeshdevicetimer"] = function(cmd, cmdid) + local data = sip_get_parameters(cmdid) + local jsr = cjson.decode(data) + local value = jsr.value + local ret = "" + if (jsr.code) == "200" then + for k, v in pairs(value) do + if k == "commandRequest" then + local jsr = cjson.decode(v) + local mac = jsr.mac + local timer_id = jsr.timerId + local flag = jsr.flag + local start = jsr.start + local ends = jsr.ends + ret = ww_set_mesh_device_timer(mac, timer_id, flag, start, ends) + end + end + end + require "MZLog".log(3, ret) + require "MZLog".log(3, debug.getinfo(1).currentline) + sip_response_uploader(cmd, cmdid, ret) + end, + ["setMeashNetWorkPassword"] = function(cmd, cmdid) + local data = sip_get_parameters(cmdid) + local jsr = cjson.decode(data) + local value = jsr.value + local ret = "" + if (jsr.code) == "200" then + for k, v in pairs(value) do + if k == "commandRequest" then + local jsr = cjson.decode(v) + local old_key = jsr.oldkey + local new_key = jsr.newkey + ret = ww_set_mesh_device_timer(old_key, new_key) + end + end + end + require "MZLog".log(3, ret) + require "MZLog".log(3, debug.getinfo(1).currentline) + sip_response_uploader(cmd, cmdid, ret) + end, + ["setLampBrightness"] = function(cmd, cmdid) + local data = sip_get_parameters(cmdid) + local jsr = cjson.decode(data) + local value = jsr.value + local ret = "" + if (jsr.code) == "200" then + for k, v in pairs(value) do + if k == "commandRequest" then + local jsr = cjson.decode(v) + local mac = jsr.mac + local timer_id = jsr.timerId + local flag = jsr.flag + local start = jsr.start + local ends = jsr.ends + ret = ww_set_mesh_device_timer(mac, timer_id, flag, start, ends) + end + end + end + require "MZLog".log(3, ret) + require "MZLog".log(3, debug.getinfo(1).currentline) + sip_response_uploader(cmd, cmdid, ret) + end, + ["delmeshdevicetimer"] = function(cmd, cmdid) + local data = sip_get_parameters(cmdid) + local jsr = cjson.decode(data) + local value = jsr.value + local ret = "" + if (jsr.code) == "200" then + for k, v in pairs(value) do + if k == "commandRequest" then + local jsr = cjson.decode(v) + local mac = jsr.mac + local timer_id = jsr.timerId + ret = ww_del_mesh_device_timer(mac, timer_id) + end + end + end + require "MZLog".log(3, ret) + require "MZLog".log(3, debug.getinfo(1).currentline) + sip_response_uploader(cmd, cmdid, ret) + end, +} + +--"{ \"size\": \"14.12MB\", \"version\": \"1.0.10\" }" +function OTA_process_action(vs_info) + require "MZLog".log(3, "get OTA new Version:") + require "MZLog".log(3, vs_info) + require "meizu.upgdfs".push_new_version_msg() +end + +--sip data format: +--{ "push_event": [ { "appid": "com.meizu.router", "data": { "business": "1", "commandId": "54", "type": "realtimenetspeed" } } ] } +--post d = '{"business":"1","commandId":"53","type":"speed"}' +function sip() + local ret; + --local data = '{"business":"1","commandId":"53","type":"speed"}' + local data = luci.http.formvalue("d") + require "MZLog".log(3, data) + if data ~= nil then + local data = b64dec(data) + require "MZLog".log(3, debug.getinfo(1).currentline) + require "MZLog".log(3, data) + require "MZLog".log(3, debug.getinfo(1).currentline) + local jsr = cjson.decode(data) + if jsr.type ~= nil then + ret = sip_cmd_process_action[jsr.type](jsr.type, jsr.commandId) + else + if jsr["com.meizu.router"] ~= nil then + require "MZLog".log(3, debug.getinfo(1).currentline) + require "MZLog".log(3, "OTA push message:") + require "MZLog".log(3, data) + require "MZLog".log(3, debug.getinfo(1).currentline) + OTA_process_action(jsr["com.meizu.router"]) + end + end + end + luci.http.write_json("sip done.") +end + +function pysip() + local ret; + local cmd = luci.http.formvalue("cmd") + local commandId = luci.http.formvalue("commandId") + if cmd ~= nil then + ret = sip_cmd_process_action[cmd](cmd, commandId) + end + luci.http.write_json("pysip: "..cmd.." "..commandId.." done.") + luci.http.close() +end + +function upload_router_log(logdata, logtype) + local https = require("ssl.https") + local url="https://router.meizu.com/oauth/router/upLog" + local access_token = rts_get_access_token() + local pd = init_upload_router_log_pd(access_token, logtype, logdata) + local res, code, headers, status = https.request(url, pd) + if code == 401 then + delete_access_token() + access_token = rts_get_access_token() + pd = init_upload_router_log_pd(access_token, logtype, logdata) + res, code, headers, status = https.request(url, pd) + end + return res, code, headers, status +end + +function init_upload_router_log_pd(access_token, logtype, logdata) + local pd = "access_token="..access_token + if logtype then + pd = pd.."&type="..logtype + else + pd = pd.."&type=".."4" + end + pd = pd.."&content="..(logdata or "") + return pd +end + diff --git a/Me_Lua/r13/meizu/upgdfs.lua b/Me_Lua/r13/meizu/upgdfs.lua new file mode 100644 index 0000000..caedc9f --- /dev/null +++ b/Me_Lua/r13/meizu/upgdfs.lua @@ -0,0 +1,231 @@ +module("meizu.upgdfs", package.seeall) + +local cjson = require "cjson" +local bfs = require "meizu.bfs" +local sipfs = require "meizu.sipfs" + +local b64dec = bfs.b64dec +local batchfile_checklist = bfs.batchfile_checklist +local batchfile_compare_upload = bfs.batchfile_compare_upload +local bind_router = bfs.bind_router +local cal_str_md5 = bfs.cal_str_md5 +local data_to_json = bfs.data_to_json +local exec_cmd_in_sh = bfs.exec_cmd_in_sh +local exec_reboot = bfs.exec_reboot +local findInDir = bfs.findInDir +local get_device_SN = bfs.get_device_SN +local get_device_version = bfs.get_device_version +local get_https_data = bfs.get_https_data +local rts_get_access_token = bfs.rts_get_access_token +local set_passwd = bfs.set_passwd +local silent_upgrade = bfs.silent_upgrade +local table_merge = bfs.table_merge + +function upgrade_lock() + return os.execute(RC.upgrade_lock) +end + +function upgrade_unlock() + return os.execute(RC.upgrade_unlock) +end + +function push_new_version_msg() + --type:4 (有新固件更新) + --msg = "{\"size\": \"14.12MB\", \"version\": \"1.0.10\" }" + local logtype = 4 + local msg = check_upgrade() + require "MZLog".log(3, debug.getinfo(1).currentline) + require "MZLog".log(3, "OTA push_new_version_msg:"..data) + require "MZLog".log(3, debug.getinfo(1).currentline) + sipfs.upload_router_log(msg, logtype) +end + +function push_fw_upgrading_msg(url) + local logtype = 6 + local msg = {} + msg["code"] = 2005 + msg["result"] = "upgrading...." + msg = data_to_json(msg) + local res, code, headers, status = sipfs.upload_router_log(msg, logtype) + require "MZLog".log(3, debug.getinfo(1).currentline) + require "MZLog".log(3, res) + require "MZLog".log(3, code) + require "MZLog".log(3, headers) + require "MZLog".log(3, status) + require "MZLog".log(3, debug.getinfo(1).currentline) +end + +function push_upgrade_finish_msg() + --(5) type:5 (固件更新完成) + local logtype = 5 + local msg = {} + --"content": "{ \"version\": \"5.0\", \"size\": \"14088999\" }" + msg["version"] = get_device_version() + msg["size"] = 0 + msg = data_to_json(msg) + require "MZLog".log(3, debug.getinfo(1).currentline) + require "MZLog".log(3, "push_upgrade_finish_msg:"..msg) + require "MZLog".log(3, debug.getinfo(1).currentline) + local res, code, headers, status = sipfs.upload_router_log(msg, logtype) + require "MZLog".log(3, debug.getinfo(1).currentline) + require "MZLog".log(3, res) + require "MZLog".log(3, code) + require "MZLog".log(3, headers) + require "MZLog".log(3, status) + require "MZLog".log(3, debug.getinfo(1).currentline) +end + +function subscribe_auto_upgrade() + local http = require("socket.http") + local url = "http://u.meizu.com/api/v1/upgrade/subscribe" + local serviceCode = "com.meizu.router" + local pd = "serviceCode="..serviceCode + local sn = get_device_SN() + local sipToken = sn.."100032" + pd = pd.."&sipToken="..sipToken + local device = sn + pd = pd.."&device="..device + local version = get_device_version() + pd = pd.."&version="..version + local deviceModel = "R13" + pd = pd.."&deviceModel="..deviceModel + local key = "2635881a7ab0593849fe89e685fc56cd" + local toSignStr = serviceCode..sipToken..version..key + require "MZLog".log(3, url) + require "MZLog".log(3, pd) + pd = pd.."&sign="..cal_str_md5(toSignStr) + local res, code, headers, status = http.request(url, pd) + + require "MZLog".log(3, res, code, headers, status) + return res, code, headers, status +end + +function gen_check_fw_url_pd() + local serviceCode = "com.meizu.router" + local pd = "serviceCode="..serviceCode + local sn = get_device_SN() + local device = sn + pd = pd.."&device="..device + local deviceModel = "R13" + pd = pd.."&deviceModel="..deviceModel + local root = "true" + pd = pd.."&root="..root + local version = get_device_version() + pd = pd.."&version="..version + local key = "2635881a7ab0593849fe89e685fc56cd" + local toSignStr = serviceCode..device..deviceModel..root..version..key + pd = pd.."&sign="..cal_str_md5(toSignStr) + + return pd +end + +function check_upgrade() + local ret = {} + local http = require("socket.http") + local url = "http://u.meizu.com/api/v1/upgrade/check/router" + local pd = gen_check_fw_url_pd() + local res, code, headers, status = http.request(url, pd) + require "MZLog".log(3, debug.getinfo(1).currentline) + require "MZLog".log(3, pd) + require "MZLog".log(3, debug.getinfo(1).currentline) + if res == nil then + require "MZLog".log(3, "get "..url.." failed!") + ret["code"] = code; + return data_to_json(ret) + else + return res; + end +end + +function do_upgrade() + local ret = {} + local http = require("socket.http") + local url = "http://u.meizu.com/api/v1/upgrade/check/router" + local serviceCode = "com.meizu.router" + local pd = gen_check_fw_url_pd() + local res, code, headers, status = http.request(url, pd) + if res == nil then + require "MZLog".log(3, "do_upgrade get "..url.." failed!") + ret["result"] = code; + return data_to_json(ret) + end + local data = cjson.decode(res) + local value = data.value + local digest = "" + local filesize = 0 + local upgrade_url = "" + if (data.code) == 200 then + for k,v in pairs(value) do + if k == "size" then + filesize = v + end + if k == "url" then + upgrade_url = v + end + if k == "digest" then + digest = v + end + if k == "version" then + version = v + end + end + end + if upgrade_url ~= "" then + require "MZLog".log(3, upgrade_urogtype) + push_fw_upgrading_msg(upgrade_url) + local ota_img = "/tmp/ota.trx" + local cmd = "wget '"..upgrade_url.."' -O "..ota_img..[[;]] + cmd = cmd..[[nvram set upgrading=1;nvram commit;]] + cmd = cmd..[[killall dropbear uhttpd; sleep 1;]] + --cmd = cmd..[[/sbin/router_reset; sleep 2;]] + --cmd = cmd..[[/sbin/sysupgrade -n -v ]]..ota_img..[[|tee -a /tmp/ota.log;]] + cmd = cmd..[[/sbin/sysupgrade -v ]]..ota_img..[[|tee -a /tmp/ota.log;]] + ret = exec_cmd_in_sh(cmd) + --to do : add UCI set upgrade flag . + end +end + +function local_upgrade() + local fd = nil + local nixio = require "nixio" + local image = "/tmp/ota.trx" + local touchcmd = "touch "..image + exec_cmd_in_sh(touchcmd) + local function image_supported() + return ( 0 == os.execute( + ". /lib/functions.sh; " .. + "include /lib/upgrade; " .. + "platform_check_image %q >/dev/null" + % image + )) + end + luci.http.setfilehandler( + function(field, chunk, eof) + if not fd then + fd = nixio.open(image, "w") + end + fd:write(chunk) + if eof and fd then + fd:close() + fd = nil + end + end + ) + local clean = (luci.http.formvalue("clean") == "1") and "-n" or "" + if image_supported() then + local lue = require"luci.util".exec + local cmd = [[nvram set upgrading=1;nvram commit;]] + lue(cmd) + luci.http.write("updating") + exec_cmd_in_sh("killall dropbear uhttpd; sleep 1; /sbin/sysupgrade -v %s %q" %{ clean, image }) + luci.http.write("update finished!") + else + luci.http.write("image_supported check failed!") + end +end + +--push_new_version_msg("test") +--subscribe_auto_upgrade() +--local res = check_upgrade() +--print(res) +--do_upgrade() diff --git a/Me_Lua/r13/mime.lua b/Me_Lua/r13/mime.lua new file mode 100644 index 0000000..f559150 --- /dev/null +++ b/Me_Lua/r13/mime.lua @@ -0,0 +1,90 @@ +----------------------------------------------------------------------------- +-- MIME support for the Lua language. +-- Author: Diego Nehab +-- Conforming to RFCs 2045-2049 +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Declare module and import dependencies +----------------------------------------------------------------------------- +local base = _G +local ltn12 = require("ltn12") +local mime = require("mime.core") +local io = require("io") +local string = require("string") +local _M = mime + +-- encode, decode and wrap algorithm tables +local encodet, decodet, wrapt = {},{},{} + +_M.encodet = encodet +_M.decodet = decodet +_M.wrapt = wrapt + +-- creates a function that chooses a filter by name from a given table +local function choose(table) + return function(name, opt1, opt2) + if base.type(name) ~= "string" then + name, opt1, opt2 = "default", name, opt1 + end + local f = table[name or "nil"] + if not f then + base.error("unknown key (" .. base.tostring(name) .. ")", 3) + else return f(opt1, opt2) end + end +end + +-- define the encoding filters +encodet['base64'] = function() + return ltn12.filter.cycle(_M.b64, "") +end + +encodet['quoted-printable'] = function(mode) + return ltn12.filter.cycle(_M.qp, "", + (mode == "binary") and "=0D=0A" or "\r\n") +end + +-- define the decoding filters +decodet['base64'] = function() + return ltn12.filter.cycle(_M.unb64, "") +end + +decodet['quoted-printable'] = function() + return ltn12.filter.cycle(_M.unqp, "") +end + +local function format(chunk) + if chunk then + if chunk == "" then return "''" + else return string.len(chunk) end + else return "nil" end +end + +-- define the line-wrap filters +wrapt['text'] = function(length) + length = length or 76 + return ltn12.filter.cycle(_M.wrp, length, length) +end +wrapt['base64'] = wrapt['text'] +wrapt['default'] = wrapt['text'] + +wrapt['quoted-printable'] = function() + return ltn12.filter.cycle(_M.qpwrp, 76, 76) +end + +-- function that choose the encoding, decoding or wrap algorithm +_M.encode = choose(encodet) +_M.decode = choose(decodet) +_M.wrap = choose(wrapt) + +-- define the end-of-line normalization filter +function _M.normalize(marker) + return ltn12.filter.cycle(_M.eol, 0, marker) +end + +-- high level stuffing filter +function _M.stuff() + return ltn12.filter.cycle(_M.dot, 2) +end + +return _M \ No newline at end of file diff --git a/Me_Lua/r13/mime.so.1.0.3 b/Me_Lua/r13/mime.so.1.0.3 new file mode 100644 index 0000000000000000000000000000000000000000..c8ebf1b6d9604bcd02dfc604546091c84f12c67e GIT binary patch literal 10200 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 h0xgjUvOf;p{{SeD3~&Gd literal 0 HcmV?d00001 diff --git a/Me_Lua/r13/mime/core.so b/Me_Lua/r13/mime/core.so new file mode 100644 index 0000000000000000000000000000000000000000..723cc1f2c0481f21c24129c26ec7106213d75263 GIT binary patch literal 86651 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 literal 0 HcmV?d00001 diff --git a/Me_Lua/r13/posix.so b/Me_Lua/r13/posix.so new file mode 100644 index 0000000000000000000000000000000000000000..648e6d800767c5db9793184fe22315b0dd8d203f GIT binary patch literal 45066 zcmeHwe|%h3nfJ*gZ37fKDMg|LoPlW*A;c*}NU`X&O-l+X3~i9A)tNMz{IHYEI5X)F ztTI5ssC^|OsFUg7jW$RfT39$l^FSXfIMP?0tkqJW=psad@h14 zO#qYTfSAYKKhCv-z3Q;xuL^K3Fg=cgh86#g`g~fihoA@b|Ag?*gU_O>p-+0(_IU?1LRgj4){9IBOo4&LHB~LB9P;C;BYPVHqiU!n#y^g^Ao>97JqWrD)BvgmwSerwx{O)i;dcVFO5Xsw#0gu2 z^L3#2g5WpmGoS$IK2Q%R46?^PI9Vm&CgC3zelhqa5ZwB=IzAz>b~v%w-t6%noX|%< zEf?yAC&Af%c=Us=0M&xt40R``wJ5fIx<9#jf? zC&(V1IAQy`RW2+CZv|}?oDqIEcn9b%!MZWxybbg*&`!`hK<@_ec$Z-+D!doG8#Dk) zfHs2c@t~Z1MEC>X+d(P8v%xL++ zppni8J`Nms*yi`r{o;{CXWV?L+};o}az7kJFUKS%sg z2hT?Mh=aca{f8X9unhKca1J={;N6gycJS8_9(V8|ghw45fc%JqBM1*W_)dfe9K6)H zMxlHMaOQWy|0ly8{3pnNLK-&h@fh&9gZ~cl#~l2hz@rZKL7x!^e;4V89Q-559CUCa z(&rugD}<*V{4dZi?%3GV9qn+4sHXEI{4SfKjL6_j0`}RE0KTL34br`;owiv z9u8gstQ`DZ#Gl~A0tfAJ1o@9U`0o%t=HM@*Joc!-NeASOI^h>1e8j=?k$%X*-H<=% z;5gFf9sEK1kAvR?9Cz>wh#z(Ei@*^FKL?${4t@#Y0SDg-?04`ImdC*#q&(A~a`5+|-vlR)I9UEUEWd*{>JaniYOn^#A9KQA0v>hn_Yh~q z!Iwe)kb@UPpFsy-1e|y9m9Tf(!9RgMaR+}H@uLpD8~H~Zd=xnB;MK@K;NVMX4+npp zMRV|8giml`f`j&W5AEUL2Y|;M{88Xh2d_c;5eHuZJmlbV=r`!#Fw*B8`~$L?CSYTe3~Sj)2I%`0On*4^8kQ?br;ti3y%R`wzg70YqI;kgi7|c#?pP+u4Fcs>F(_; z;O^dBqBD^xU{7#wcT&Y-iA<(96-%btT65j0UWGWgk}4+Za8h+9a=BER@|t69U5U0$ zHiv8?mrQl`v}QM{wqz=s0B~Yiy|UY~g@9B#XA^H}?armU+o4NGGS!;PcDHx%ynAbV zN4DFDLG0eXo{cC=U#`0+q55=IEO%~O8n8Fn-MdNUa@%@a5gLPqH@A1(sE}b>s<)$H z0Fl9TZJ9)CE|JrQCPoRQX!_J%mvT~D7>5xviFC5HEupAKwxFFYB6w>~Ws`|STESYG zWOolL9m3^ItDM@HN+lDmF!r_{t$3!Vy*s0zQM-nW)SKASmrZ2aTXU^CTL|muP9)nk zfV>{)UeunozqvKpSM-H8)DFAAHeJuA zj_!_>>e)mMH+3hIELK-4n?r?V({Q(3hw4nGHX7TsZb4Sv`a(x0kzibScDv?%h6@yw z?#yUIvHlS`Eq>6wwXZj8t3TGAbt*2G62RzND2C?h&N^dY`=M=G<)#6}GijZ9P7IwIFr;z?p_qHS~9FxAo4n@gJ1tr@gDwj=1( zmfofiSNmogT6qtY#^t23elwdOBDQ6=rE{vSt0&ctzmy$|ZB2K^HnwIHIw^Hv`{~n2 zmuO>O2fLA+$ukY#n~;6Z3gtwDwZ&(v5nbm42?R&P@E&6w_V+~VnwXJ z?gq%_*^S`(z`&slaR=7@XTo3mFNT_Fk8@q0HnLnipERyK43odNV>(?*luz#g`z4on zyc#rNSe^ z8-+IsZx$XE-XeUx@D0M_!rO&+2~P@73(pDPDtx=}yzm{ucM9(pJ}7**@IAsG6+R?< zpYZ*{4+tL-en|LX;YWmz3O_3RnDFDm$Aq5{ep2}J!pDWbApDf@7lls@Uy}d zCUmUdGU4UI=L+`=Um(0vc$M&g@R0Bt;kClU!s~@M2wy5ZBD_&}lkjHYQQk~@!Vd@^5q?PcVc|!Fj|x93yb61#w6jlDEp7P8qt(Ij&y|i(e!beSjtp0+ zXND`maR&{n7!zlKj`R7_n*8Sm+OJwt-6dg&Kj6lD@yC>9;!Sifsa|%ek->P7J5a91 zO9Sfb)y#KP$NgMsRer}nW&Xi|u?KgAO4ahvHU365M=h_Nr6LRFKJdALQWem*c*<3P zD&rpBxaO3xeTs~SwTx?O_Nj+zW~pZ{pQVB|E7b~|&r(DFme-IKF35r|VvE4TD+X&} zj~eyVa7Y~-4yY$#pON99iZ2N6jHuHOJU9S7{Q>BI8uBkVLmeCYb$Rm-4TSQa94ODX zg{~P~;d^LszN*R(4opt2T@>6CSD}YXcU^}~cV4fA2M>3`t_vp zQx@&6u3S?6r6N;j8yTw*lxj0Nnt5{XxjHbsCYd z36wR6*-A!T7%E2@%TPwFj)&)>%-5?6LY?a3P)c1A>QnPV0aYGqRh6OZ)J36N)!a~4 zy*{)>`9oV(MW|NI4=J@bPx%+C+vV2ZL;W@RM+PQ;abaAla_mdN@|R`hrhgRd3~gOc|C3aT6!RPqe>V5Df>diD4sXP3&kU2~8y;bt1eS-c`v{_@Hn^ki`AJ&~x zU&{0E8zfzY`gea${i*oBS0~e_A1;KDcrH~i(osGDYus`*A1)O>PCwLH{@Jsmk!2$T`0Q^+@yI=f0 z`6J`+7pc9qA4I##!;&>{kX@b&lS#5 z0s6cW=WGXzQ-$+OG&G+)P#U_FJYE`FK<+OMT}D1yTK_t=LiE~kbXuk94v6G1!ohk|$*p+qeRUF%Q# zF2wvKSfhN0oP2|Uh_B4wqSGwgdA09l-FNyI9hnj*;G>=O*p0D~V>5KX82%K>_2dBZ zM}0+n&a?L)j^*wkq-Z|a(LNMASwIqbKYZx}w|)+_sJw_eBozUb%llWFsY zuhTYof~J25Wg{QTrmnmWyC~PcIS13%VW%nnjdUiy^0|HytO-oy$q~etfXFQG{O^8Q% zj0e56Jcc7f|NZLm@T6)C->*tT73v|#_Z8$vk!}vsxMPsQL497Y9{&_&xcV}TZP(Pf z8Ha5fe&GkEu8+aj&Y+C-uwNK$tQKvo25l^eb`^p>AzS+_WSe?~-_z%4ciPg}IwE1p z7eE;E)z{G`O&v#k{qz<1VXz^tw7(MXr`}O-EDQY;^*~+XKDODvc!l+iHmt{{r_kmPqOCs(-#7MZkUsCwNn$1HV5M;uXBtd*XbbV%}j~|X$Pp+WXX;&&b1w@17%t+ z!)v(CQ+umJ>al_ReLFB0i(nmq`J?(`HJ%iBXSTDBE3|b#8^qJ_NT>HQeja{)$qhFv;MH=+&X01jW z&`*}VqWre)?Ejw0m*b!A1Ca;&caBTUn{}yeU+629Iys$hqi&{@6?t)7;P_(8YRAQT z$&>!ab#4{bt;{R@@bX<;``Yr-FN1sXigOY8!-8ObR4sMp8$MO0`#z+5VwSqP?grFB zerH8t4!)CfuAO(IcCX&WwmfytRkq8{S<6(}E<3L($GM$X&BeL#2|7mEF4}>=Vf^IWYwd{>rrx;T z;JRZRWn~=(YxZgzvHsYW@l-bV2Qb%orZ8Sp7d>WESJb7R1E9W`x2>Z=kaM;N96O|IalzxjbONWOe zdR*mNgmWRLMS)Bh($2!T!m|A9B-R2v9N!2ckKq1@wmY8Z^#=z7z7bD)rls7xzXJ2| zji~CCx?i{s`#1F2DgF$3P3r%cdbRq8^M!Fg>B#=iNi%==7d;KeN)<+()s;N%U!$Znw?MjO4QYhOn?70{OgeRCD+F| zC$jzik-srMa~O5H0Jd5RAHh7Mx)Ht+!8pl%IL^nk?V$@W);N2I>0?#-4-c?U>91Xm zH5hygeTyGq>>p-f91qtmseV7^8@UI68iKq5$fJC{UT%_cXQ|}x%H#ZS?E2*)%3*&; z9VmB!8XCS(k1C-T>da$xMhoZdonA39KK$i<`g5{#E zYp`c#%av?sgD*XXeD@A>Oy_!-Wm<~7t57E94SBjew(m?9@-EtkI-Gs$^yT4vhcc@n z^MJ@yINb77icGfU5X!=K%eF_|8CSPkcstYBw)(=Du|WgU*s+7jsQL^{`tQ<*4=8&E~_gQu_(&h4XaxI_GnRx%*Dh4R)=`e~SCd zvnv;77geZbWz@5YV@1H|%XvNJ%t9Eh!9O3-YclR@Y=94!sUWth`2NOTy5bgd+^e3i{B2q`& z|I*luK5qRC?Lya&yPkFZ%-V(Vr>yO)T~N*_Y)PBxI)j~zH?;uPJxy8~q~V zuuZa0;@XvK*&t-uv5Y!!t~2{=ajtXZyEEx8d+oN3ICHEMZr*vLpUwMRbF5SUQOvi| z=KG7H-Jt()$d7fNAM+nS`d+`&J|%CIrTUDnL$*)l!)mNxBRg)IHX6|D0NRY}zK8!q z&!c_Z*W@*>u?lk$$l=(Aya$l?0P-F{-lkvkn|md@j~hF2eilYr&cmqhbbA-Up&`tn zv3^3^)b+P#RO&Bsfc{$r|IX3RP&X4NrH-At-{9Ex6&W`fw+3@UrnPCdXICv^oiogx z2TV$uYL2f68<|cX>Qv7w5N`Sf8pg;v=ES$MO#TlK96;EKn;$nehYXvBsj~V-Nh4av8r^&b+VE$v;>VN3*{A3C{h3e$=V#(^&teZ?oUGYlav9yy(wS z*oxyG^cZHJ!23pQo4UN>PnU2F1OHXHXM{gk%jf;@E2SJAc%5Y_GxHTV9iUQAFdo|) z{hs%I==aD6er4KOt>f=4qL-bU&b^O&;d>SP_5k!$>hZ#yl>K|bcLJk2Z#~ArR_B_V zp8ig;-0WAV1Iy}`owjhx&M~43aVJaUI_$u5zE(T_^>2#pc>8(g8-d+^*L+Vp`Ib5P z+Og4FUR_ohAA_(R$ESH1f9)7m*tCbC~ZH$7-h!<6hAy;!!W#5ADYO5A&rTO_^t-e}cTi{E>YXecsL?&wShD z%{I87`J*h>?^~cpGsf*E=+X#XwzA*Byf2FJ-1UV@b+Gy@{RTckzh)VO4H45fh+ptN zDRraI!~b=^`sUy0{WRY-rcIbW1?jbH$e4WlrAt{R?%hsKW^dv+&9v9~qu69X|Kz_E zcEx@K_D!(fkUhke{W?AJ()Pf5J*ssx^D*03@ctd-E;=Ckm^2aHm!sSRDB}RiK7g{L zK1@HcUdHXC&l#KXzQlDX7j?32^HJx%1ly;*PsMi0`4{g|uzk)&yPYrPuEF^{Id8=I zTseOMdgB2qEq@T_cKj(v986yI^|Oxt)PrT7slBuA8ISkh&;D7l-E$0Jy>p(+c|O~N zwh44KbJqx8?^4v2oMGe)!^XbWC4#UZe3D@zOJhH5M0^-|FpZm*KC{y)1Nv+&lkraA zdIe-N&e*hR73$c??svk2VWSt*9d%?=CiO)cJ+_sL-t_&gPP{$vbH-!*QCBX?#=2n} za@RWT*GygO*mX$>=7Ig^SnK4E7HuDf-E99;DSL0sn{^31Ftt5L*f*oDrrvi6JMvf$ zwhhzoDBI2_ZJp}2f$~`UdF#f6zq)RCow`*bk8{n9@CT+TL;r2}*@NPz3imOMyoI}_*jd}g%xRL257@nk(rIKJkUqFv zE!;I_-Sir=f`hy-e$B4Df4=U=?7qn**f*hFrqv63Ii_EmItTi>>}AkL+e6Dw)g_$Pgk!n zx8fX{;nc;B7rcJj@mG#z#&4ECq3z#9D=06K_W$NVS zPpro-gixm4k7*H^u7BD7jpcUj;N6>IUK|Ie)E#VM{ph!;yIE3qx?l11DR_P^G}XQl zZC|bpXy2)JG`0`o-ut=R?&3wRO`yA$7eu|eeahtI!bRSC@Oj3Bs&AOO;GXjYeLBqN zNzA-HtiFQ1n5U;-gN_WTLL0^X0%OCtZ_0kKw8IkB63P!PP;9?J?7s(V@@C$*pYxoE zPD>jpwf4ZZ{;po`z(|ZYwf48hkFQfB71M1I!16G`KbZ6Q?|{I{~MkG zleyigBepLyZDwENlIjDxPo0Iif7Q}s(}eFsIL8`>@0W1;e=~G|J_Y}`_a*14NEz<_ zhUeklRtfI8%vSBVFSa~PpJ5p|m*m)bGP^u9*SW^I#|d1!5S;Ch@z`GHZeEUY?LqXP z%6}Z;Jg1y7oU=Xg+-*-~qT`rn|D*={CL!#b)M6i{(*MP3-Ctn-$3CReZ|+el|MFVQ zVfFlp?eQ4OXno~*S+_I4^=SKc4-{jH-iPD$GF&G&eNjYw>(%$Vpu+(48Gt^8xlUO2 zQyTx&)Dibn)}#KppTc{sv^D3}lx_12qTNhsgEhFvNB=(3B$H(?TuMs>WVSGvYR^-RFZ|}|i9rMK+ z5M@2*Uis<6Gu8XK_c7a@Ip|xaJ(w@o|90)qb^lj>U7R;A$ z)z~fQA3|UB@BsU~N9nH(qiol@-{X9*EXU`~BBsrweX@S-8kPMA=Ss%EIPVXX+Hrq_ z$RBllcB%MmgZc*UDSQ?86lNI5Fs5kVaD18jIP8bZG4Il0L$(hxzQ(yLb&2PkdtCNe z75H~e{xhiG&kh9h{JomXLRGo%E$UTQh49SNgJ`b!t)5Y=SM$CK^?&^x>e(7TcY$Vt z=PjT0w}D@*o<-jo@XbRz@G1XIv(!K0rzd=PcE?=jdLR1|H?dzY*WtV`;lHWuj(=aY z7x%gF+??(M{#oYv4YW7LL|qmm?2!5v>gR8(Q|eo&+qf>nC8*mT)M2fzL#6k# zYAPPD=JPC9AZ#;u6?g{x8j14;$o-=K@#^12H|lG3D}ip8iEcsY&%Ghey?9=#&m;Og zSTpV`gB_WcWhG}>?OqaXU3SM07G0!$Ye?JSe{0|R(4x4LE(p0L&>wy<03R5D4-CKu z2H*n&@PPsN0DWcvzA^w`(EI8qE9CyiQ^vR4b_^Y`zxm8?1KMys+He?i(dnLt2!>zX zhOKX~oi06RJ2n2ncACL6pJ%gAFWRCm_q5YE+TjU**3(WqP|ga}7j45i7wt1gec!(o z*QaZzWztUBo*DPBes0Xz(zeqf*Wbj?*;YALzy@fqUqO2{&m8c113hLSe?E)GF+@Mp zg1HOFkcBTRziqb-2a99Z4%8RziumYz&At!YtoHHzP~q8;oAfhe0U!5?;0`9-X?qu= z{jg7&c25ZVb35I#1S0%>IK1!i>ODI88ErUUSjR_Mb0I*uEh>$qV-e_Z!*b`CW6N$KMtgLAw`k(T$;X0jP|WS_xu zFmKw0d2)X$0^dX5(64RCcZv|kI2_ZO5a#MKoqp_pt#0WNtJ~-F`%CQi4&A+k&n;nY zi@q3j%6(btdYo-l#?c_!CjHXRp+0h2&!J@Ad=U13asayMHg4z6Bf~}gIS;Vyb)iw3F^RkfxG9_d75LP=~E(3eymHIA8f8|WZS?4%oA;ZvYF>m*9Y?vxn4HK zR#q4C5!Z(BP0C@t(muA0vVFRG-U>Z!z3zF!*oA!`*C;i3hYT`?58drwWct5D==%;1 z*P?z|hTt*ft5;7AuXFUK?{P2X)pg7@;BLI95m`{VYWE^z&&Daw)v*j?)0AO8pNT;E zId9W-jXXF{|MOo}l&!~njnDE0k6}E4KFt5k(1-c6{CbZTY1y|?KI29B91H4<;ietH ze~_;BrX|&HJ4635wp&cONNeh$QQxbf45st?Z1MWk`r-5ZIv?yGyy(^^`bS=$!?Gcc zw``w6**0)43Vj!7ed99UhApKX0wrZ zNr`W1Ny&1~JJ1jFnG*es?=V)syLL~}Hz4!1L+JmXLjQko0DdrKZEE}Z=mFzLT=VXM z43ranf_c}0eF2}#rXFn5cK#JaeO$u69(xlt`A?vpAHf{w@qyZ#7B31O3;0g{TSeL2 z&E{S^_GQq=vE7^J)!Shw`UT5Mzi5Q*czz+P3I}CXK^Ema2RX-OUG|H~hz{polv!tE z4e#kIc6`~Ch3%Go7yaJbZnaIFKJHa(P^_bk57@rS_}XL8#rhh{R02J$AK3mlh;p*+ zCQ(L~?c!gV_G-f!=Jw4e{>12O_tB^??H|VTo3&`Gwr}R%8vE*CSjJSV{}7(t#j{F$ zHuBg5*+uz5J|CI!u|J|M=gD5^xZ6%Kmt*-(PqGY)7uh|X!@R#ZxSVMV&(cm#mWBA- z6J;46FwZ?*0AJ)aTmB%*&GP>BFVPOL$51JCIRt-#-{^HT<7>O3y`G})<5^o>7f8$N zRfxy;%7l3VuCx?67-oxj!!YHs>(bzMR9D@vr#)u9gkE zS|6{U_BmHm2I`i3!0d%#|54i-tAkyRqPR7M{6DIySWTluk;2N?)f9bXnm`%una*)G_Bt zRuAUEa2+3gplNqgpLJ$CpikTJTJ4uMh&{mLNMD70Kd!?db2y;)0nK_(`9%+@{|gFp zAv10;U;5Ni_|$s%lwDU$)yaKE3iCG4^HI4k8C^JUVEs!!OMTFP7VksbI)9721L4kJ zZQmS(o}9lD|F-kjAWVGj`K!|NSIRrr{MEh3?47^b{*v=WJAdUIhjSZu?#g+My>92N zcCOR%MX4*;hrSVnPZayh;(KoepRxT8Y+?KG=65ft-lyjjMZaMf_d<3?msG#V@o>8?5BAB851Q-jADM>x=Cm{Cjw}cF6gdCk+T@j&igTYwAAhyp^e?t+>d!fvt26dc z@}2Ng`t>Tk{!+d!tj+MO2i9o5&~2cbaGgGP5688@+v;;RgM0s4kud)CWa zk+u)%E*BZxcR@aS+~YXJ_$;sMOT1oqma;JB1bq8p9~+-#r_OIeKD_4YbDN_Nb!c+( zVO*xW!jq2mu@m)S^^57e1q_I?WX*eZD+HHvb1!>$YaREW#=;{^d0J9!$PvBxdU-**mUEJo92MX<9;a9C>)$?aO@wuZ&m+^ z`)-rxsm%D#w67)eu*jS{jm(pf`TTjvJT`-#}(r)Zic$hZ3OM*x2^BfGnDxZ^gDYV`l&wSlhfHzePC68*#~}i zADTOZ%yP(_dmb_?XOQWK%mwEmGc<$DO317_51HW^WCkEJbRII7&LFb}GHcI6X44EZ z!;o2j9x_`*=5+0+0Wz1Khs+HkbGkk*0-25HA+u`+nN5({d>%4$GuSfmhT)dC2UaL1r8>+s{Mh9+AoS#B^Ww|Ix?o^T=}h;4bKwJP-XwW+-zSGIQr4^N7ft z&eyj>=JxZDc~oTbT`}*NmWRw8=OJ@UWKP$=?}W_$^N{)c4E7v^%-!c9^OVT6KIGQ> z9>{$3w~%SqV0P|i*I;9Nit}2oU-(;KT))7^dM$%3oJEt9e9r0hyJo4ymvS!1vH9$z zf6;&Fb^Ysiai5rTW;%kche!mB&JvimTy&l}-!ATF^Jow!n{4NiErw6b1;Cnsz9uIEu;B_9n)`Rc% z;HU@R<-vD)@EQ+p_TW1__;wFo?ZHhRe47Wa^5B&oyxfDAc`$aFru2_*_u#jA@U0%a z)PvvZ!MAwu5)W?h;F~@8CJ%m#2iJS>jUIf12QT*Eum``{gX=u_dJnGk;Ojj2S`U7c z2iJITwFfWq;E)Gjkjez)ivpB~UzpcFC?7y+0$r`W z?f4J}j##X-jeHB~I;C1kIM1b00erEelVQE7TmV0x-`Cj{$hIbPYGZ4A06*rh)A#jm z>cvmpqig~5Er~$&!ZtjCCLfkSxdRIknh7l299YtXGPSWuUxZoEuv^S%bHiNZISa8YghABUthV_D(3mo#D1#*5ffxI)5!q*&3nE48ZIZwA{ zv-l)~j>FGzn9q6u%hx{4*FS*Gw?N2E3od*s1Yw15nDAVG)dXCBPeQj(tpnP&mT$h2 z!uCv02j`b2G&f&uF<)3g8sHiVcd}id zK~{28EXQ=(vRQo;}4m)aP^H&$|)R8nPIHz(R--R-PH zegj5CD@2X8wf3R~LqD8%_on+;$f88Fb$qME#%Dhe!#6+>#rPM#SHo|Bz_(bnC|fL- z=`(iTjPDTX@7&;eH=GDPwK5;lflc`t4r3OD4+HgR`v`9nu0OqlwD=Z`4sVl+Xp;+x zWNSK$Y_na7WD@Z@6K(i%2=z6Nra$fhtiS!i>--iBIs0JkAw>B7jH_OmOuzA!$?c$> zpxvM$&;ih4&{5DBXq*&yW%5PP8BlrfmB|I50H_w!a_uXVaZnPp6|{r2hch0pF%Ir)&B7an2ZTq3PjDK+BclJRa^ZgA3xrn+ zuM)2QaQwjnf3UzGEbx1`fbI7!Ki#KPEoO{o!5eQ!|BZRs@zvfG@ol&w_yCM^RNf z_}pbk3!VqgfsaNYoBUl$?Er7aN4@*O2j7O@OahO9?*l)I7h6WaTQG;;fI<94$UO{v z>=vX2Zw5aGeq;%B0dK6wbHwDpC&7<5;G2!$C&SPKdZ*t38Nk~y$3FvJ2F}3?Q+ibn zUKxYV;Gy?m{)znjH{u%b&<(H)`18=C27Etw7<>ohHh`CbN5CsDbQ&U2jsb+N)SGvp|-;YJi<6%3Tgtifa0JeXe+26vdyq2>>?Yv*GIR4hpL(P-&;t|&ML=Ot0E8Et^u%Q1{m>CK1{wv8fQCSWpgbrI zii4t{2q+8+fOz=HK@(}n0F8l0K_j3c&>$!eN`vB{C@2C7g90EwNP#A>b{Yqbfkr_i zpdrv8C=W`5;-DzV9ub^}K>?7T*Fh7QppS#bK%<}$&=6=4ln13jaZnT#0r3cf2S9#e z&;-`~?5-q(Bo`gA{1ueUJef1C4@4 zKtrHGP#%;9#X(UJeFvY=yCqO_+nx8kDR5)mjrbX1bKx$0-0$8@?#<2k%xNYSz{Nm) zUBmS^gcsM>h3f;dqw*$Rth6Uv{8fK!dVX01D}*WO(G3;kSDLaWM}8T^}L{j1XZi~85z#PD z=d?4)-`B|S`=_LL`xnl6r#q~C&W)!#Y#RroL;hdv;>xWI= zrsrIKrt)#_?+rhOD>gmX2;ThN`sG@}8}8~?b_c`H?O&V&W0z;t9p56jV)N(R*&BYc zq8QFK4Z^3`FM=yJ{mv-E&)t3x-92+S*9Knuxc225XQuLT%`;Q}TuXWTp9rp4{kdlG zrg!t_TFV>m)(_WAUiq&7a_!~qA6)rdqrFPLzo=jHyBU7&`sJF^Yd=>%uC-nzA6IPo zxh9(_eSMta=gxoGJOp?4_NkINIKfXE)#3Z+_=Y)nE@OC?J>!?0d-2`zx^{ljy}M1- zb+zJa)PzW6crK&rAg<2d<8na^VVOjd@l~DvLUx`0!gJl4)zNjxuWw^k)$!BWh?def z0_qZ7v5pKsA796tmnd5l+% zc;)%&<9*SI=SMt0;vJ!~a#(p!0gF68_X5Tq#6FM{kL6(8!%jS|_o^N)>V%+ze$U{X zW}ton#0wx^0O$6wb~%a*%r`6njJMK8k}!sUSI%uv7vc@kf#k5|_#5Dt;eYme`9QJm zd5yAo{1eEYN1$IN;<<+n!;q-UV8a-1!UAv`kK8er>e`L9G7_`>Lse}TmYFK+x&`M^ zi3<9qQw}T7giig-{)0!U#%JbJWF%7V}ngZwpD}Kr<*ou zuyJTvlOumon1cxpeEC*$wk00?vKbFqqXW;?m~DecDZVnxgKdCEDZX;5kGv^BDc0>g z=+`_-bx(zQEMV3O_9XP46~pIiOn>B2sxH->erNCk&F!C3B?XrYrRsGyNb^!<3_-u( zQHmbgoN0)IkOJMJ(fhW502M6=_mhUf2kp9c^ zp1>LRR8GLv82^XB+wU`dVhQT=r@-7(!OOQg{O7>+2xmDN{tIyKsq6!1`rm+a4+TG{ zq;VNI_e?mgVfZ{??vWf5d&IV6K_tf}a7d zf;eIH>|!Y=^k9)qFbI)Lpgx3Pk zg@2jyU94^b=AM9+_cmbe0a&~em}~w)Nq+}0*Z9_c>wvkYxB9*lm}~Uyl7Blezx5tK zdfJl{A+FJ*B5xZo*W`HRRG0T5@X-kJknoQHbIlzWd4s@QL)-fJEHKyH2PFO%h!Nf( z@xKVnHM7<431F^~0}}sfV6KTR{!?JCfo*#@3d}X{sKoy+FxR;21%D5iYue`p{{t}B zuvL=(3&32nT6_Ksm}^uk?`2@FN#l}!cB#1!la%!HfVt+}fOzzW*8y{lxk1A5Nle8x zWv$?9V6Gt_6?yf*Tr=AIZUN>RF)iWCfw?B!FL*UD*MLou-<`l*^BtG)7GSRNMg(sF z=90GupJLeFdn`D zY{zGgmOlgLzC91()4*v5;|DAB{=N;r2zby5zYLi70c>~`@Q8!o1U%~C#lTiyKXPm! zcEWE59(Qn*>3yg?9G9ZJ?*yK3!Z!l*{sa%|zX@31vk;sG=6wtv41W-q_cnMCe+)R{ z;61=m2Y&%L?%*#0rycwRaNfaR0k-=3q3_p#2c7V50uMR(JHR6jKEd=3{sHiqgZ~NG z9#`Y!CE#%x_@d>(kr34a`T+`%Kj6Au0+ z)Atw&EZ=v4^?f#+6aN)3@4fLLJ_XGCaXg660CWC}|G*chSAZigZ6IL1^gh2}&5R$zVqSi(Djc~6-KT zzx*+9)JcCBIPTzQfYT2CHgMj-$AJeOd;)mL!9M^Vaqy3TM;&|$c+9~Qz~c`7H{b~e z&n`zHI9TIgc`gCg_w5B=4jgd8uK?!#eIAT|HE_hiVc@8PmjH9UY1-#p^>*O66Mh?T z+QDmq^A1ihy@R&^4>|aQz#|TR1bEcJ9|In9@MnR?9sEbYZv8#Z_?S=fs6c)GDX?8{ zvVOUTG~uKl1LiXlJZO*Sf%S6}f`86%IT-#+VEcRpaS8mC&s^{zz8pB>;A-HggTuga z2j2{wcJNBzyo2un9(3>q;2{V10FO902R!QF4*-uj_(9-t2Y(cJ!oi;cRvDJYl;;b; zeg_W&2ORt~aM;0Yw{Cqr3mkF6j{`>?{5QaH2mcdr+QE3}x;w``=(;w%1CYa}bVmnH zW3d%2cdd;zuU^*@ivcw{s6D}r@hn~xh~;|tDoZbRnWwoFi?yd>oypY3)?`d?W5-(i z@X&gyC!NHL0PS^eX$UXDb6BYi9y#9%w0N=7d*8A4zMh_KR)v*!Hfjx4tzEWeWz3?Y zWW7(WB`5X!B~YI4yTp3%fO(fb%_fp+m43c|jar3w59HDBbRV7x&ty^=#WOsi-iOy^ zqRu-%QT>h!-cG>PZCTCDn-Ed`kbl%XX&&wCY0Yj@QF$>SYF`vs#Rt-}_PPAJ`X!5( zK#OhWt%5u3NqY*~;e{o>9uqYw))~e;@nC+3T4zt9g?AiQ70%Wb&Y~zd4jeb)iT6lSaR?+Vu^la-e?_h zqI@e#MdclhDC=Bleau7l)J2TWtLA+Vo|(5mqWV3NDD%ql{Vl%b1W$>X3sJs-g0}_q z%O_F(O)Jgx>n8fuCPv}A2r6pdUWuC5S)%4G7cdHm7T$xwOHt;Xr@HQJrnN4N5ed)v z=o;=bg5=eio^8|;R_)mqYwJq1;bjxN1{Kxsl4ynT#zM5i*ti`pv_$nQP*Hg?hD8(` zuM>WUwK;z1H6`sEx}6>FB9Y1XP(<2&zMwr2GT zw2_uID+-N5H_}22$);lR>KN{4wWYV=RTUAC>}!qXQhnJ(M&CoCH&cF5Tb!}bjMtr& z_tLBv#yWH&H16)r?HxBpB6ub$b|Y+PnnSEJjgV}b(o97ochk&pMZaYci)~GJ#tM~D za4KepI33*yycb6mGE~B}2Bt;P$#@aD@SIxPx|h^=Ezi6&RPbZGZMU@*&eYqQB0W3=&=qx~7jqYl&4z$<`VA{R9LbAy(j(}ej(yl>r3+LXN~aVFcb9da9E;)h zZVbi9NIBqFQh(AVv(DHG*h=&}XY@PWJ#{b$wTgDjj*VLD_o>hq^c3Alq}f4>X|^yL zJ2L=wP1=BX<*d+%(_7F-Dz>O@l})1w5OW0QzV+qsQ+}3>8@@37jyH>4Qx3VSHvTS+tO3X?Uy`r5K-KV3n z(ooh8RuP&}CWDvF@IoD4A46->M%Bu|Aqs5`>Y}|&qcO(_(HFMpM#%V@p~!YSpUM{NA5)ohvyV zitE1a`;YsNdtN>Lo`>)Gp6}Cnyv~*DBUdlJCK8Ew;jhAL@|0?v?Rm|>;}3dX)blEv zJ>QE_R_C2-%0qm?#*YS}Qi&lisIRy5p4`C+Ztpxqjgf%ASdAC~F54?u(Zo)j9x5Pq$78_3j-$=Naa24TN zLK8v18H7PXicmvXO?W3kzsm#QEd-uV_)Ee7;W~nTeF2#KziTq)d95~nJMe143PKy< zMuL1|Heov9{e)Ng$&UYT{$BlO5UX%-&L&=E({)b1qG#FkYs&JKJdaRq^Dh9t*2XWf zFa&WTXM~9|_%_n>ZQg|z)&m=DJOR9tFo*CKg6S7P{oYPE$HY8uG4RcVcM+~9+(bB+ z@Fqg|dk>MnAiROFJj?_xwecH(uP1a68VIWh?<0JGpx-ir`qKi!I|%w+Mp$WLFaOUM z5q=d`yL0HjK|1dJy<>FgzuHMlUl}K@^3Oz7Gx(|elV#=W=un%g0{U_9=M$Ad)Kn1u z6;4|8=9Z<;avVbV&y}@ze_6SF`p{kc$M0-<@TM~SbIQ{1FXQ)yvhqHMzW93^@`OL7 zN6P9qIOXE^mySG2KVC+^wJbeYhX13mF`(M}aans$mX)t9EB~XDR{Oil1(qb?4pR@6wPSW8|_;btZ?+puq;{W0@{k+ZLPvswQ>MQ+yM;}W6yo}#HW%X|< zOYd{&iNDV~^pyTy8GfdW{!wtkpW45rtp0byLZHgeFKch1Q+_`28D;WoPFqdEKH%Am zTJ4l?bo$GNGX9o0^u&Kf8NW|E?TeoWpy&3#t}=R`FH38l3V%M4D;#=iZ+%z@RQlSo z`ZqcFYJYWEdPf=lPs++aUPk|uW%AwS;H&-FW#uDf_IZ0*`RB^$uPM{_ZyoyL-*@OK zy{N2wXPG=daLUEsYs<=ym!(fQ{D}Um%i8;=GX5VdtDko8)!y7P{Cmpqzgx!NqfT1w z-&&@R?Pc`Op*;Mly{F6QUs{&Fwygf$W&XIL4FB#j{?|M8#b3!utNpJ#_$uE<2!ATy zT-M$Jr@rv_m$iRUS$e;NFM8QBea$Pw_gcGpx(2+~l>o2xT}!WT?M(NjS9SFdr2Cd$ zzp%TvC%v?NMRz(VJAFxO$J%xgXz%X&P}=JrY+u~ku{zyxdsojudR4klIjyVG0|UJo zMSC;pp4R@}j@#1%tsT96rb26Guzz*`KwnqSDubQw>+3c37Pkt$yK9BX?rXoZKMk|3 z?dkRvU9FuRYck!ZCRaJhOS?WaiQ08(=VZLIe{jWAV$EQ;Xj_(h2G^{B%TMqP?@zlSEx>(!DD?+wU^mNd{Zr5bVtilqI{TL|ok3-iK}^W+={}n3S%% z`lsr(4)jhXERzlyqqv@~?nxq@mkv5&OGB%yNmdN5l#vGlX_3Gov)It-OZN|=Pq83b z?r85G46M_a&UCkTSUMR}q7@i+acfWd&Nb`HeB z3YK&$dV9Om?LC1ujJpmRlHwQ!Pvr{7gFw&>JoU7%3HTc54arbNK8?-g7-6)l2Pe;9 zLeIq76@w|PVrWt6RHninW3YP$dfP<|Qz7NbUd7e2iCZ=UU9Id(rzf!}S=>6=fs7(T z#Xw+nZ@(de!+_J%+uGgR(LT@xU#6Z>1VShS>`Xe{XM}}m^y{r^?`kC)P^VmSn)1+x z<#6c_dYRttZm+*<6+?p8pYBd~40u70_d0redX(%-ciiE%cXXstSbw^w)BHEkYw}n2 z^{(;m?CTmxlWgzwR(A9ZbbIokw6~%Q+-?m|Xma)7KxglrhEvJ4;w~ux$yS%kW(8ky zmtj^T&6I+=dhjOe^&Rc-E!Bn*+v6r35oe!tzBTDJ7!YPwtm^1!?Vs1*JMR*uIuuKI zt!d*=t*tBi`%zjuZ8-7PR+~b06Sq*RxOU;f*30H;mv-te7}~=L#2M&MMbp`d2~*jp z)(k0x38!R?wTp;TkIUJ zvmNb4n=I5mQ0at)%@)qLaDjzM3tKE)WZ_~9Qx-0@aG8b6Eo`%})56sjR~hItzy^++g8G3pZIfV&N7Gw_3Q(!mNcmE!<_{ZVUHVxYxpc7Vfuj)WWAMJYeB7 z79O29Jlbeg(oaLX`wgW+SkG=3u`Q_v#`NJ-@--<6BagG zIN!nr7A7rhv2c-vi!Dr9xYWXB7B082&B9I#GZqe5xYojT77kgs!NQFeZnAL1!YvkV zwQ!q-SqpbsxXZ%b7VfcduZ8<8+;8Eig-=;{z`|!NJZRx_7Cvv`ix%cAJY?axg~u&C zVc|&&J@)+?|6&$aSy*FX+`?K5>nv=r(6_MB!X^vnSeUS|*~0l2F0e3ZVT*-}EL?11 z%EF}4pgTDaQ6ZVNLO4p_L>!gUtL8K+*==y`R59KJRPF4P%9#CtXSu5Lm0 zTNy#-umM31Ue^k8@V8EI29GBMIRM@u$c(p9ki+#&f*fd#2=ZWHiy)KZRzVJww+XT- z%nEXVyHl{%^L7dHkYKkUhuC`rG1OkcI?iqcIf&jb$RY8lAcxLR33AqYK#&Ix&j@mm zdr*)=>gNP~&wE~Qw&%Slc)sW51TWwmQ;Fu{62ki+m=!AsG%AP2?`f;=4X1z9dN3i1G?N$_&cx&^UI zLXZRNWS^y+Duy=%gTr<1K<$ab_a;PR_ywTX@zXcs1wSg4b|^%7vv$#9zhP@_X@URf5A4LB>>y8zu*e& zFW8Cw1=HAHkVE-{f~&BbK=85}R|=yd}lgPrUh|I`E>2hZzxXQaW*v=huxz z=19^9Cnm+S zxGKNd)K3*F!8_CQ>WAWi4qi)rnZ1v#Zl>ERw|NXM$Dycp>@PTI3=MdP{RY~lrE8(}Wr!~;_wxENd2 z0^@{?l`($s`oc|;3tg!_$s9qR&nT}gRbXjXud<0N&n!v@mGNwT^vyREe{@Mp@m`~k zRFP$3b@Y;4agjHCq$)XFz#d~&{`OK$f0ioV*}ZQ9S~R|LG}BZ(kiXBlu$eU}RK2nNOgP8gwCEJ#2rL+V!@V z-YJ+=+uKXh%R5L9HCLIo=h61FD)-Tu!(SzNHOQAhzVWN(z9c>m(xx{AZFC&!@m;Em zK3F!^hk3#kj%k-NmTvXWw;jlb`P1NUIy$(*)|Gw!O?tIFgypwTesK1IeBlqKEU;}G zd}gxwb8Ov%$pd+|2GwDm7&I!8W2P-<8^BeEz~F^r zXA4k0EeX%5Z2l;Ac%C*xoRHphO9wxy-{!~?mR~@*WO>|`1)8!)&bIR$JYc6HeX;oO zv?HkWcU_PW^)92K;&ZGVD$Wx}@$LTX+z8QP{slIhFeh~4pMd4@;Q9sq# zt-h!6@h!%Wf-$`12kZevpS~pi$FIDuDBq7#M|yt;jE}*W9{}zLvEh;Z)Z((tQJn?%%<3RG;`ClXR3E*+yq9yDt2^xph zeqCau6m84qH6F{a8%9S;@!UwM)*oREy@^>Xdl}pJD?IPhjH|;1&mS)NapsZHPnV)^ z$`%(ehSiL2H}cdaHk2AhH-OiX^Ah)$>JnQ@eqw#0BDo>wCGRV_W3R?O=^)g3JU&F5 zL#2kq5cP&iRfHPCSJ3Ua_f%fG*Ze4+YrKcdnE0=f-Ji14H%V7{kCYCS<$sOzPW5H~ z>HNI1vc04$(3R#sV>fhR=wq|5IJnZUY&e^|3kWsd1EnhO6QwVf;mD44;l> z+gh4S->GZE?%vX3Bysu**yy+^63Wggx|)zTcrPo>FeudKjNH5-Be^wsjo_Gg$MHD)Jv?-RV6l-{)O>I zJlDjBOK*eUI5;(N)+7GI;67ZsRDKV>aK4Njq3o{(7k!)gTxGLJKS_U`mc$Q%bpY8x)3ps~Mk2Yu$6f_zWw8P#vdupVMO$;)PKve}l>D1AV4xXPo@KXT=) zV&pr+g^4R~C^n65DomqnI%O69w!^Qco_t5Re`{!@3SpXKw)(}iIV};4e+N{~nDr*~ z!VyVzGi9a?>WhaO%0+h-Q2D>I@Rt^z2aIV9@w#)77lx5Bu<19+_p<0@Pd5J};xpk- zvdAy1p(9(KFC1)uajMJZsTkVE%6?gqWp{NIcKpG_#9tr##;oG_+~S3$eAqv}JHjSiQp)waK#^HO22O zM6MVvRQeAcZepyP8Gq=oc$BG|{}ntazQyKMkasb0)<0trY}kzr8SJauft&F0@g1{_jwW{8 zP&|g-q^oH|+58OY3Oz}O6(gtW%ERAh`1A?d>9o3%PNXaGTbX<4a5Z|8US7$4qqTU_wMVt7g?A-z1`;ceP>{I>lHrg2^ zU%0COIC#q>_jevB7{Ay&t9V>8Vw=z}v?o!A`sOHDGHmrvBvDNZ7#y;8}E=1z%3-SlyRL3_Rp5cG@u*cpp zQcc;&gM}t}XmONXeFpHC=t20xt)@K2+|9c43hBEj z{o=3Ee|6%a!+B(mVzavVBc+DiBh2%U;D3Sd)y40_c0;)u|30mAE@+6~cOiL3-;77q zmB=sswLv3{*ZKHw;=w`#eq4iZNB#BMFX!v9PetP1Tn+02`sxJ@34EEb!`77!MC+%d zcXD<-$~p2*Lc?{OgR}l<@J6uH60-)0&>zg2fpKD%H}sM>I#g0R;$@23OK1!r9f^|G zzP!O>jpc3538&VH*EsP-oNG4|_HoXw{h)Box(Itmku%bk&C6$YJA2)$Nu!sdY##Bm z`6qriadgBfe>>$WQ(wG-xc0kaJ`>lm+kn}c6r@y`Kb8|Vl7&UFsX z?<^hhb(=%;2IAsj41WsR%ogtZjjO}9y zGMS$%&PTVO zLbv16DRi2diE-5W61KC7FW8GkDWz3%M4z&m_a`2MEdY(H;`yJVmX(RFn(-JjA$ z0XmXhw51ErwiV*XcrF!51oFskaVw+binh@nZ4HPQ>Z_eu#N{W`z-8PqdGeDde@j0B zM?NMz$x-_4#Mthu13wWx(Q2oD1YJ!-Zux_7e_K}fhsdUU(Uaa6z*8I@RH1`ftz|@u za@oM}oFI>N#0v2VZPkVTSK-g@6BiaAGVQWwz-P4I`2y)$8}lHvL>t*ktQAXjga*RDQ@_&s5B_0n-9?`2g}(P5+O0cl%C?VF zuJ(m6~f-i>Spzv6S6%J8W>C2|sd^P+ox41sIqmJKfwe&>0%Em?OvlgHE zv%c1cw>WztXvciy(mD@6c_aCY;MMH2(8c5!Ve5uzojs>Go6vecP8nnSn9h(xnUr5m zzRqhBW%)7kzfJzrK>1jMH(X+UJGOWs&#wtDzLV#|gyjUCqeX^-^9o}J>>(QrP_JC) zHRQQE|2gZkl%w+{l!yF%>%`QY_gUi7IZdCU^QyMt!UNRN+`bqn93IhEdk)RFJ2b!O z(EPZidA{&d(lzS>Y^V0Mrv4!H_G2^k^DlslEIK2tuQGPoI%Su(;nH;Mf*%zN$Ri)s z8L4Q6w)g0C1ETv9d*bs!r(M*jE7*Ey4$|0(jHBEP9D{}J;4 zp8V&p;u$*O5McwMh45mM9!v=PV-&ut{Gd;&59%zbhP3=mex^PtTdH6E4B4b##>o0A zZ+q#(w4=VGc|d$!gKQD+)IJ)auAP(3I4B)yKH89vsQ%~tfZCNGp_t-dNP4PP7@`E_>_uD#g(mm)<`Dc?K(mI>;YVu{{ z^T}Ue@x$~zmex6xaXxEg5)GyAAf1ps@P}~Tl;3rrlkzd-_rE!H4AHs&bI#a$mGp?+ zE8^MWm%vqA{V+wo))4ZaU1fdd0C-_vod@0~=w%WUyiQ2}U{9$r;9`gFryRNq$(N5p zw|-d}-7hlp7eC)eS#ReCWPUS=c5zx^Rtmn$usM%n_KsL$`B zZU!BjwN?iC#d}iw?#;OrWzwg{+8A`a_@gC_1(M545}@akuQONaXq>jEtDN!RUu@lR z#*13`RXzEEx#!>1Xp_{59*q5v_IKXV!SjhlUFcS#--;dad`#l0V9M^Qhi* zyQLV9f3g&5_KK_tw68(k!=a3E6sCFOw|L4NUf7kmg-(e^nu zu07rlz-t)!q~RflFW2F36NVn=`P{(du7cU8H=w7=)5zl5>phh3!tZC$4(pDwXL!gQ zuG!@0vXRDwUg(RLEO0UFC)q@4^~qzl%!_X=y=3Fk``;07bk1UWyBWXNN&obhvwm=WLH7(yKcRm_+K@HLURU(#2MPK?BmO@NS(<>2 zwjcPmAIRrJzxOGh)5iV)KE2G8@5^U2HlB=4_3^vNlkcDP1I9J$`_k8RKb!yQuO^QA zDf#}*#rL6?)tBrjwc!7qkL@URKE%D6#O565tflJg=G-#MW{hx8h&=jcX)$TV^&E#Y z(6MUPYSWNQ`q5cE<7WM^bVRy=Ii-@a2W?t*^cA=GGr*6u-CwxZmaBhU=it7|iJwip zA@zvi`3>+K8P)T(M@n(#R?{yt%-{6Uq4@AiIpovWps@oVsDGBU>tl~ty5U&xdCFsn zM@ms-`Hr=z=0nMKtHrG&ExvDYc=`$TcrrqJ=-Yz7R_?2Bpk66ZEA9bbHCOrCR7|92E`LAYauy=-D5PSrc2wsc8RlQf zi9Lm1j5_(^UH&aUr$3po=5wNzU`}G}iX~>3IQ!;aRf_vIn(sN6Nbp=vfxc#VrY+I` z7wQSd(D%QxrV-!a-5TY$AfIHabbS5mR=yDLbo|^`#*fZR#80sP2%ax_@rO%aaN7Uh zr)j^@wqL54>T91aYhQP{;^;*ET;uAaYTFs3W|7u@PI=+jqqR!}+LBTIk>i#OYpqrKaR8Cv}rH$@L}C$YXfi3WdW}4#Hp_)!Tk=n@k!kExj1M1 z!fytD1Nh?$=f3nD_DiLP3q#mVyz5zo$20$0#w7WI#)MBAzEZ`x_~5ir)@T`?AGHk^ zDu#kFUHwL5jP7O4gO29Ns-fZ1?`dE9eiimNb2)ORpe4H1esGWCJECp<;2*({GB-;f z7tr6UiSs%^_15@~5_`R(^x6-873I(2pX2KT|J*N{CN6TaHhJ-a&ih}rxbZ9eC&V@>FC=cU|BfAgQO(|?w3=yz}YAL{gPtxlO& zCv_@4OJ_POIq_%c@O;a=tDD=&lMZ`25)~Pydr+k`l(BZ|74qqt4b#*BG)9KK& z`O@Jb4&B-ad$>d(pVv5YX@qeo$@zDMHx}hAL47=$3(g&0WBlXBVic%8(~LhP@NI7t z|4T6b;DZ`dIhQOoFy?8Uud~B5y@!v4=Z9x{8;;CMn6+l10lP~6IpCa6yT_y6QI}@a z;%Sc@*4O!?PvCD?96jRTYYnEccJfRVJ|Y>$H_l!w;`A}vsexXjzrEzQZ7-dP9;&6w zp%l+-bstajw4MWHBN5VfqFd=tW3suUmsI&u_Ay@l>zR)mk~%+G8r;JJ?=j5>{wGS3 zN9Q6HJlE42H3g5eye%&&|Gzx%=+{WAO{FOh&S4S{lx`%ymNT4b@EGTLqsqBzb(Fv1 zWANzXZQQN3T$5YJX@LH2k&dyej9M3Gnf*Er*e_OMZOwH-QJkR>+0 z{g7F&Cczoy`C}V;f}V8ObQb5EDe}=b;|y~H`)Kkw1@U6n$sfQ2a8%aO82U_4qYy{ba<9r&%L=ZZ&dea zSk9ci)7y)0<15~-BWHSB2pf<5f_jysJZ}Rw({FSEC!@YYAMkRF_2@Cu zhQB4V`GelKphrIX^m49^j=p)woXhQ0n&-hhUq;`-Gt5fXMUGK5?i_QA*p{jt2{;C>y? z;)}CsuNuDB!8d-w9*w(zlvjDfrE7)flV^QJzO#Wm_r7nD^nC=CaeiaYC8P)GuU+*e zAHGaFqV=o%dj{p21J!5#ARL~>{ofN4UsXMGZv4R1okwJ1zK3t{*Mlp)F(czA!C0pG z|BI#_?gtWAAJJGQyBM2hsNY8YA@!F`QS0wFp@XdIs2?TO50(e>Fl8QVX3f9TXz#!; z0{-Q9*ugyC(wH(6{C;owIr!jNx}$#3o4UihQgqu=>JK_2&&xN_jqZLh$7YzXhfDHr z(N|m20sXb`E8Er~;}ctSe`HMd)}09ap!g%8ku4d>rZLjUmEyj{korwV{k#5LWb3lB z$w#D9@gW&2(b*~>{GT%u+;?b=hkcNVt5d-}ol0gOsw3WVc=W2JTO-FS@TRsx zU2%@}Ri38PR={7#SIDROK}B|oKjRNWN`I^rMStS4scm~{PAcFtF`_ZUtH;=5b!EKH zu)NYCPvMp4*7e8iGXWGuRpysA)^u@np5k1FdgA$f(L>iq49_1kJe%N57+n*Y{8ID*ehQq+gd; z>37SYq2FrJ+mkAw-xtKMx94M}a9=OJ!?<`4W=`HN0m#YYM6oF1h$Y0r9_^4AyG=A))LmFN}>sarF2=hOCf_nGv*|DEb zjNQOF-SP`rp@9c@VKsO@I$-X6p@%qQd37(a26!6~|6@r`|HUqPA3*u5fLfoQ0o40V zDUbcU7uJAfYP2s}M&=F#8| zp7u)I-7n09r;49%9??30{xC)#8DpFobhXd;2TN1_xf3PrgYHkb*CyJ!8z$Nh3kSNQ^8|6hZv#C)2aTlG zho9b z>4o+nvIOo$?q5OZfZA78-k1DtMl|J_? zMbqI;_;0to=p5_%$-24XV>Yg5j{{D8r;Yy*sP&}AozuPV^A}bxvhBXlalXR($IC1! z_{_VF+3ltA2cCGI@z9K!X5E(LoD#o|@c!j=-h0sb(a!UE|0oeFL}x#Gc*$kpvJc@s zo$5&TV?4Xb=HCeq=-KQE#LJ}5K;vy?Wfkxv9Ib`omUo?LuOhDYSmV~i(-F;ekrBpV z*`dw&mCgW)HQ-I7{@X=&h&?O3{?z74M}PboeGYoUSz_~JKDJ3P#@n>YuOmJk9Nqyb zoK0M|HhUUyRaay8^A=zHaXG@YE8p87eEA-@qAPsvbei*D@o-`xckJ+yJ@gUHNrr#= zg#5(N2It=$dKpWPvj}s4Lo}YDTz$jf)6Q;NSM9H{_>4~mUwQHq`MU9|WWfJ4i&tfR zS3Y>2@+c4aknHlm4F2II;hphpFXLt1H+PiGdmO3Yj9TZKHKUtz_-|2XLzUTJ?`ZUv z$>tNRJHG_a?CHn8Ab*L^WK3ml`{tvE>(APhkDhh^Vb9;neBZ|&mUm&j_vaZBUhwv8 zEPa`Jq5p`N4_Th1hmTRFJlWufHm>|b#N|hNe)yTP`nTEoTEE_D>qtkhmh5)SU|nAQ zD08#f)9lISStC|QW~Pe&X7`+`8}ql9Hd3GG8)ItItPyW`;>goK;~h00?h}kHdcHfF zE8fU+2oP2QR**XuY1CKHm~yi?CWCw-=K$^c->{9{)Y3|2TP|;p5zsc zFGwz+(u}M5E4>^$Pc9x?if|; zIogZ_ea;Wcgja_S?nY0-)mVn?27h#jca&_MDyNQcREK*orjBUrvvuOM$=OZy>J`jC zDSS19f4A|DHa?R9$HmxsHsQj0cc+|IX*>;EU_5{!ObkFTP+V`^f zId+VAA2K@pF*Y8Wte-26{}deRitc9BM-Q^I(t2m*DCOec@a6NIML4$ncH+YI_5^$- zP5t-lJ=$uGDUm%{);ihz@6`r%L;jxx&(Ndp_bAiZztR_x|M^Q9PhK}%I4@gWsJP_b zoHzTN(rZ8et-SYGMd=@`561Tlmo7AIy;o=7)#&j_#_#VTt9U8KGsC45S$vJS-nml# z_&2UAKJPzk`usR$8o$*4S1}ehdc2E*j?DfPKD`9ii`RoaiU2Oo4>0N z%MO>gH+59y9&{VP*LXcyjt>c6b-9CObSWM7*|?sc_7aaeJRM+Qt>wXMjD_1`_|qCh zeN+6Olzos{^wbYWIG?)}-j&u|Tt{5@dnWr~LhJR-xeoHhpVk|lECknnCU|x&e_6`8 zAvQ7eQX|pg-_eHph0f2^hWz?}2Y$i2m@{5zR#2||z3n?n=RsTV1Bowh2>-HW>MLsd zT*|jl&UjwLzIoMC{zC4}EsIBuPb%*owE@mDpS&dZ_DQ!Vn$I)l_vm~=JixQYHTldqesrAn!?OY2_bOa&))>nSo|n+t zjXKotd;|&+?eI0z~XtzziA-ebSjuJlA#{7kE$)3%l!=*%ydou*x zpJ^d1A}l7%ZyPS<(4X3se+z#p@0xLUeoS}dXN+z*%pP&fJeQ#?Sfg&vjiMvzMR$OA z?%;1rF#%=>`$R0e(}g*Z)CXiFWR3vzMwYb3m?Qbb>4&Keax7T|1!3u z5>dtwJ$HEU@ORNmjCzq#`2=@o@eQ?KkNkR`q324AkV9j`{_Wg9E7KkKBlPaunEKBb zfy>}gy3-on=#f5rH8Se{w)$KWS;U+0)hF~UcNu!2ADDh}E_#q0fsW+Y3yUA1eetU` z^lrw8U4$1u%{yMUt#R9y?$k$VL*qshZK@3qJ*t0*-apcg`mvXp%G*nx^x=)7kKCh& z!8P`j&**uJ}9daDnOZClv%p04g(>Tc2;QKrk8PTghH)jm=7 za@&2qbXPxQWSUPu)w$4%tf?BnZ#oVB+2HHmmUt9@!j}zpADE*UaV8;yhXrtb)PfgjwwR=`|SzCOW`67N^ud(H0XY;-^c8TD}^0!9npS102 z%v2xOd*snf(C?&^@_zSU)R*6TBf&f>*}qSo^bx)F{(|PV`K&1!1I&D?e!(3Z-3cz~ zo}}g>?hEp_BAA~DT0{AaYkK~C@9XYoF4r7JodmvzoZA1W|1RYo>r(k;iat%b+Ld3v z4IcGuRcZCvPJB*r=IeY8zSL*u*l~PnOkG%90G{S#_1PSk?-~fYGbkH`&+pV$D#Q3n zJ2gYB!PQoh@veHPFdy9>Yui!!Iel$!sbG9gveuJbKlwg&<+~N; z-DvE?*<>a5GHVY@|6WT=eMV<*l{WuwC;uduT*e8_9A@e~MSiji|JXG|$nee~zOZv- zN2!gqUe(a%+_n^J!VGHg86CS+euS15( zI;5|)Y2m9}XX$#kN_|pzk%at$_p#UmY(fSPJ(=f8*fxU<%L(f&OprIuoNU%psbD=7 zMSj^QlHu=9>8__UXKsL=+Az-nh|6YX9fj_iX#ekyP8`*{z*;kTZBzaDUGONHKVS~7 z=8m(@u=UOzebB7k(5Ly^Nikpg8ah;o4CBdX^Km9vcCZBv7c)GdiJ@7?)X8zRNbjD97#+s-*iVX54=|;9T>y@^^A5Y3==pNoD*DJ%w zUA|t~NS-&!IEhT=oCo=}Mv?8cMnS(t-QUsLC47g_ALTx2+i;0{q(^R*?&&v1--vUe z)xE~P$Dplo^K|Q#KTtL~_OaOv=X1AT#F1O;l{bh-_><45ou87HuGJS3)Lj&gnR@P@ zoeRTI)FWlZ_}3Oawh$<_0l&i?@gKycz$l|VR?%Vv9^<} zqxiO^t1(^r7Z1`u>%@ZYQB8+8-KEmGlb`!iN&BGq5b@OTOP|+$CdVeD&=g;yxq!IF z>#%)|CE*zMApC0mKF;_NpGhACKEs3f)2%-~2kzY&rdU;#1U9o5tVZ zBV5}hnP;1+yPUcs)D8W?9nUvWSGJK)OP<@X+hN(xS>O58KiwMi9_ohUxbHt?o=yJf zSKOmxGkQAwIj6z@F!-adfS(3mJZXIUEIft&xdHr87T34m2mU_I(TP*{CYM|PkZ&)g z?%L3|HJ&4P3%;#!Nd8p;4Wo|?KH_u#l=dTS_(^iO^l^BI`N8@~^SR1~ZJsxT{@_!2 z8_8<~U%F`m#twPKIY9YH0>~b|&?2)Eh9m37_Bmt8{F6xQ9AXFIxQwPUDcQN8D=6UZ_+DT{d1d#;j2IPV5WvV`-R)?QyyTmIBIxs$YH z330a4r&|cy9G_axyB9{DR3J|(Ib2x7x=ZqCZiHTOly#HZH?l$VZwOUG_ZM2OJfpat z^UEP{B%}UTlR1Y0x5B^o@WGMc!n=_sl~;K+XM4(@+~TQ0{g1b@+v&AOu!eStfoj^D-G zh6{^AJ4yd3o;^70+fPHYDsx|9zSwN8lfH@b?<8%F=o za`i8M-wYk`|2z4r@QHuU@*RC)7~bgD%x6*lX3$ATcfu2W>F^-<@<$iHmP?=VbDR+gSX$-l;eT-Qn_0~3V6Bk{4&i!uCu`xItciD~_*=THfwP$%rO}~{ z1+9(c59TZmem9K-YvbAMt&}f0G=FN1+(2F^7e1ZeWYa2FdLwD0Q|inhJ);djaB!6O z5z=!`vmX9u`1jC}*1~oF7xOOCh?mv3fpJ3k}^O#G$;7)~4`5B|E zNu9spZa@Fzwcc{|lAGRj!+WUbEgo#Y$$SUSzieJ&ZePbGtFsrudi>Fb(v4f z`FvEVuf6Xs)#<;zD8@&wQF~aKN))Dl^~T zQu#nf#vAO+gkPodR*=`#!>7)=I(=KmU(?%}7A{|nQkEBd364K1)jfsF_p8!fcclGH z`v70c>j}P21vi5|xA*W#DnIzXl=$R>Rq3^qxA*r$^Fp}4u>T7GLK0!Sy(@0xlUR`I z?@z-;uYXY}(Y%f;TKV1?->c%wco&JM3+G+-mVQ6IHbadx?OiyPnB%YN9T4HEZ;|;c z+q=4jCRto6!>*v)^a)FY4`i z83!Sc*6uGWr;RB^!v}M@CIj>1c-^Fx(6`6@_MT3k4~gC3ceMBPV22fHe?{-WYF{7c z<7YlR2E@_Yy^^ZX97wOhDs)`qa(w+yRMk2t{k`3T z<|}i^657{%a1e(2d_FU`vb%j1it6d@nM+^my(8Uc1_n9wD^XG{qgEr>q)&K!sI3HdzsE+|Og45MEpaC-Y+TK(jZ@ua2r8m9n)~hMf*mrkFcNY@eW8!Pt z^_9Hy_IE%G~kRxp%y^w+H9%W>8Br__VgJ!jlEVp$V&k2httZ`w+Erid~)|- z&twlV`cq!0OU2#Nu!97)x?6r7)-n3*%FH#xa0m98+qDv%IqB|RKBG#Xksb$ox(v?h z-i!n^c~dn_ZdsnG-PzkS(APUSAUmz-V-gh+;>KngM-45f%>h2Y802BmxpXJKk=no7 z3_S4G+1Cq$PNr$I4AbPj40F<_%qJh^S9}7}{F=_^Txm9IvJY{-%EDkd@ZkILb6}2f zy&KrbMT7z1rbV350go{-4gpWDMCP4K z$niW(@%fy;^3U4^k#m4754}H5O!U@~$(6-k@yNSDy*gX_9q_xP|J>gIJcqvo%>V!4 z|GPR^x&$9x7FcWVO`+BB%gTti0RLS;A7}y20fxU_r1#_hdw~80VJ>TFMv!&aauNaD z(!#R|U=97E8`yaj=QqI7Yo_p9NUQJE0QGAIF0k>skMR8GR_Fsyye}x%|LRl8)~WP9 z%0~$!1pU+pcdz8kjr307L14;^>_PeSq-)+ze*^AYfR6*mfhU2_B<|$Ou%q_}Y3Kx&kf-E+Bsc zV@DI|W8f_kzRH2ey3i$XAFvy^3Ah&62HXIAv70jLHItSus(_owe};Cp0Ji|MK()6U zC_eT9JIQ|vxEDQzKaE}682{?-rJfxJRpyhfOXA1Cb<&5GyM^>((#tID25u!SeeMT8 z{0))cME)jVv+7eW{ck1x98mPvU5T!M8KBz9fVU604LZ*NcLLR3l63{3mK>yn^f!^!Lm)?)`1^V5?Dy!zB==Y95 z&kF{jM|v-Ko50%*p7fmo%6{{K();t!lim-aPw9Q9qgTGOdxhV0?R##1BOB1m=3VMv znn+xBNh0wUzo{j?qN}~f=Udl(e7?B7KmA61l^5)H4t8}jjj<@aG;vAuyu|#u34YCU zS6$kCX}bOL6>s#L=gpsYnJG;)Cob`uTpCx+@mSjE$48DaQMD&^TdFJEa^9IsuiJQ;8&+F^$)K|3UrB}DEWLuFAaQHHLA1gJ36UK#S zNcQ!b=2>QU&}1*90up@W*qhhUyGH9{Z{Af)uAj%oxUXvks|+mk=l?X1YL1N(G~X($ zW*${qznBf0M}@0=&BF?r>B0O9y1|PBHLoizJk8q*nyT#^p4#2-2(8WsR?Z#}MM3Y4NmPYkCE| zzXC`6iJrb2JqNr+#KT|M?p89SW8rBnH2~fbn+6#EB-Ueuke=3i%hxlWE5rUoPvLI~ zA)e-S|GvPVE!w2KyU7q;wY#w@;%(eGr3>MPq`ze231#vVUSAM3{|isys|08<`USt= afj2iy07H6$YBT&@^q}W`CrkiCy#E8?M3BD# literal 0 HcmV?d00001 diff --git a/Me_Lua/r13/socket/core.so b/Me_Lua/r13/socket/core.so new file mode 100644 index 0000000000000000000000000000000000000000..fc4064a74d7e33611e03324cf08c75115ba26b34 GIT binary patch literal 306460 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%0A3x|0T`fZUXe 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 literal 0 HcmV?d00001 diff --git a/Me_Lua/r13/socket/ftp.lua b/Me_Lua/r13/socket/ftp.lua new file mode 100644 index 0000000..ea1145b --- /dev/null +++ b/Me_Lua/r13/socket/ftp.lua @@ -0,0 +1,285 @@ +----------------------------------------------------------------------------- +-- FTP support for the Lua language +-- LuaSocket toolkit. +-- Author: Diego Nehab +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Declare module and import dependencies +----------------------------------------------------------------------------- +local base = _G +local table = require("table") +local string = require("string") +local math = require("math") +local socket = require("socket") +local url = require("socket.url") +local tp = require("socket.tp") +local ltn12 = require("ltn12") +socket.ftp = {} +local _M = socket.ftp +----------------------------------------------------------------------------- +-- Program constants +----------------------------------------------------------------------------- +-- timeout in seconds before the program gives up on a connection +_M.TIMEOUT = 60 +-- default port for ftp service +_M.PORT = 21 +-- this is the default anonymous password. used when no password is +-- provided in url. should be changed to your e-mail. +_M.USER = "ftp" +_M.PASSWORD = "anonymous@anonymous.org" + +----------------------------------------------------------------------------- +-- Low level FTP API +----------------------------------------------------------------------------- +local metat = { __index = {} } + +function _M.open(server, port, create) + local tp = socket.try(tp.connect(server, port or _M.PORT, _M.TIMEOUT, create)) + local f = base.setmetatable({ tp = tp }, metat) + -- make sure everything gets closed in an exception + f.try = socket.newtry(function() f:close() end) + return f +end + +function metat.__index:portconnect() + self.try(self.server:settimeout(_M.TIMEOUT)) + self.data = self.try(self.server:accept()) + self.try(self.data:settimeout(_M.TIMEOUT)) +end + +function metat.__index:pasvconnect() + self.data = self.try(socket.tcp()) + self.try(self.data:settimeout(_M.TIMEOUT)) + self.try(self.data:connect(self.pasvt.ip, self.pasvt.port)) +end + +function metat.__index:login(user, password) + self.try(self.tp:command("user", user or _M.USER)) + local code, reply = self.try(self.tp:check{"2..", 331}) + if code == 331 then + self.try(self.tp:command("pass", password or _M.PASSWORD)) + self.try(self.tp:check("2..")) + end + return 1 +end + +function metat.__index:pasv() + self.try(self.tp:command("pasv")) + local code, reply = self.try(self.tp:check("2..")) + local pattern = "(%d+)%D(%d+)%D(%d+)%D(%d+)%D(%d+)%D(%d+)" + local a, b, c, d, p1, p2 = socket.skip(2, string.find(reply, pattern)) + self.try(a and b and c and d and p1 and p2, reply) + self.pasvt = { + ip = string.format("%d.%d.%d.%d", a, b, c, d), + port = p1*256 + p2 + } + if self.server then + self.server:close() + self.server = nil + end + return self.pasvt.ip, self.pasvt.port +end + +function metat.__index:port(ip, port) + self.pasvt = nil + if not ip then + ip, port = self.try(self.tp:getcontrol():getsockname()) + self.server = self.try(socket.bind(ip, 0)) + ip, port = self.try(self.server:getsockname()) + self.try(self.server:settimeout(_M.TIMEOUT)) + end + local pl = math.mod(port, 256) + local ph = (port - pl)/256 + local arg = string.gsub(string.format("%s,%d,%d", ip, ph, pl), "%.", ",") + self.try(self.tp:command("port", arg)) + self.try(self.tp:check("2..")) + return 1 +end + +function metat.__index:send(sendt) + self.try(self.pasvt or self.server, "need port or pasv first") + -- if there is a pasvt table, we already sent a PASV command + -- we just get the data connection into self.data + if self.pasvt then self:pasvconnect() end + -- get the transfer argument and command + local argument = sendt.argument or + url.unescape(string.gsub(sendt.path or "", "^[/\\]", "")) + if argument == "" then argument = nil end + local command = sendt.command or "stor" + -- send the transfer command and check the reply + self.try(self.tp:command(command, argument)) + local code, reply = self.try(self.tp:check{"2..", "1.."}) + -- if there is not a a pasvt table, then there is a server + -- and we already sent a PORT command + if not self.pasvt then self:portconnect() end + -- get the sink, source and step for the transfer + local step = sendt.step or ltn12.pump.step + local readt = {self.tp.c} + local checkstep = function(src, snk) + -- check status in control connection while downloading + local readyt = socket.select(readt, nil, 0) + if readyt[tp] then code = self.try(self.tp:check("2..")) end + return step(src, snk) + end + local sink = socket.sink("close-when-done", self.data) + -- transfer all data and check error + self.try(ltn12.pump.all(sendt.source, sink, checkstep)) + if string.find(code, "1..") then self.try(self.tp:check("2..")) end + -- done with data connection + self.data:close() + -- find out how many bytes were sent + local sent = socket.skip(1, self.data:getstats()) + self.data = nil + return sent +end + +function metat.__index:receive(recvt) + self.try(self.pasvt or self.server, "need port or pasv first") + if self.pasvt then self:pasvconnect() end + local argument = recvt.argument or + url.unescape(string.gsub(recvt.path or "", "^[/\\]", "")) + if argument == "" then argument = nil end + local command = recvt.command or "retr" + self.try(self.tp:command(command, argument)) + local code,reply = self.try(self.tp:check{"1..", "2.."}) + if (code >= 200) and (code <= 299) then + recvt.sink(reply) + return 1 + end + if not self.pasvt then self:portconnect() end + local source = socket.source("until-closed", self.data) + local step = recvt.step or ltn12.pump.step + self.try(ltn12.pump.all(source, recvt.sink, step)) + if string.find(code, "1..") then self.try(self.tp:check("2..")) end + self.data:close() + self.data = nil + return 1 +end + +function metat.__index:cwd(dir) + self.try(self.tp:command("cwd", dir)) + self.try(self.tp:check(250)) + return 1 +end + +function metat.__index:type(type) + self.try(self.tp:command("type", type)) + self.try(self.tp:check(200)) + return 1 +end + +function metat.__index:greet() + local code = self.try(self.tp:check{"1..", "2.."}) + if string.find(code, "1..") then self.try(self.tp:check("2..")) end + return 1 +end + +function metat.__index:quit() + self.try(self.tp:command("quit")) + self.try(self.tp:check("2..")) + return 1 +end + +function metat.__index:close() + if self.data then self.data:close() end + if self.server then self.server:close() end + return self.tp:close() +end + +----------------------------------------------------------------------------- +-- High level FTP API +----------------------------------------------------------------------------- +local function override(t) + if t.url then + local u = url.parse(t.url) + for i,v in base.pairs(t) do + u[i] = v + end + return u + else return t end +end + +local function tput(putt) + putt = override(putt) + socket.try(putt.host, "missing hostname") + local f = _M.open(putt.host, putt.port, putt.create) + f:greet() + f:login(putt.user, putt.password) + if putt.type then f:type(putt.type) end + f:pasv() + local sent = f:send(putt) + f:quit() + f:close() + return sent +end + +local default = { + path = "/", + scheme = "ftp" +} + +local function parse(u) + local t = socket.try(url.parse(u, default)) + socket.try(t.scheme == "ftp", "wrong scheme '" .. t.scheme .. "'") + socket.try(t.host, "missing hostname") + local pat = "^type=(.)$" + if t.params then + t.type = socket.skip(2, string.find(t.params, pat)) + socket.try(t.type == "a" or t.type == "i", + "invalid type '" .. t.type .. "'") + end + return t +end + +local function sput(u, body) + local putt = parse(u) + putt.source = ltn12.source.string(body) + return tput(putt) +end + +_M.put = socket.protect(function(putt, body) + if base.type(putt) == "string" then return sput(putt, body) + else return tput(putt) end +end) + +local function tget(gett) + gett = override(gett) + socket.try(gett.host, "missing hostname") + local f = _M.open(gett.host, gett.port, gett.create) + f:greet() + f:login(gett.user, gett.password) + if gett.type then f:type(gett.type) end + f:pasv() + f:receive(gett) + f:quit() + return f:close() +end + +local function sget(u) + local gett = parse(u) + local t = {} + gett.sink = ltn12.sink.table(t) + tget(gett) + return table.concat(t) +end + +_M.command = socket.protect(function(cmdt) + cmdt = override(cmdt) + socket.try(cmdt.host, "missing hostname") + socket.try(cmdt.command, "missing command") + local f = open(cmdt.host, cmdt.port, cmdt.create) + f:greet() + f:login(cmdt.user, cmdt.password) + f.try(f.tp:command(cmdt.command, cmdt.argument)) + if cmdt.check then f.try(f.tp:check(cmdt.check)) end + f:quit() + return f:close() +end) + +_M.get = socket.protect(function(gett) + if base.type(gett) == "string" then return sget(gett) + else return tget(gett) end +end) + +return _M \ No newline at end of file diff --git a/Me_Lua/r13/socket/headers.lua b/Me_Lua/r13/socket/headers.lua new file mode 100644 index 0000000..1eb8223 --- /dev/null +++ b/Me_Lua/r13/socket/headers.lua @@ -0,0 +1,104 @@ +----------------------------------------------------------------------------- +-- Canonic header field capitalization +-- LuaSocket toolkit. +-- Author: Diego Nehab +----------------------------------------------------------------------------- +local socket = require("socket") +socket.headers = {} +local _M = socket.headers + +_M.canonic = { + ["accept"] = "Accept", + ["accept-charset"] = "Accept-Charset", + ["accept-encoding"] = "Accept-Encoding", + ["accept-language"] = "Accept-Language", + ["accept-ranges"] = "Accept-Ranges", + ["action"] = "Action", + ["alternate-recipient"] = "Alternate-Recipient", + ["age"] = "Age", + ["allow"] = "Allow", + ["arrival-date"] = "Arrival-Date", + ["authorization"] = "Authorization", + ["bcc"] = "Bcc", + ["cache-control"] = "Cache-Control", + ["cc"] = "Cc", + ["comments"] = "Comments", + ["connection"] = "Connection", + ["content-description"] = "Content-Description", + ["content-disposition"] = "Content-Disposition", + ["content-encoding"] = "Content-Encoding", + ["content-id"] = "Content-ID", + ["content-language"] = "Content-Language", + ["content-length"] = "Content-Length", + ["content-location"] = "Content-Location", + ["content-md5"] = "Content-MD5", + ["content-range"] = "Content-Range", + ["content-transfer-encoding"] = "Content-Transfer-Encoding", + ["content-type"] = "Content-Type", + ["cookie"] = "Cookie", + ["date"] = "Date", + ["diagnostic-code"] = "Diagnostic-Code", + ["dsn-gateway"] = "DSN-Gateway", + ["etag"] = "ETag", + ["expect"] = "Expect", + ["expires"] = "Expires", + ["final-log-id"] = "Final-Log-ID", + ["final-recipient"] = "Final-Recipient", + ["from"] = "From", + ["host"] = "Host", + ["if-match"] = "If-Match", + ["if-modified-since"] = "If-Modified-Since", + ["if-none-match"] = "If-None-Match", + ["if-range"] = "If-Range", + ["if-unmodified-since"] = "If-Unmodified-Since", + ["in-reply-to"] = "In-Reply-To", + ["keywords"] = "Keywords", + ["last-attempt-date"] = "Last-Attempt-Date", + ["last-modified"] = "Last-Modified", + ["location"] = "Location", + ["max-forwards"] = "Max-Forwards", + ["message-id"] = "Message-ID", + ["mime-version"] = "MIME-Version", + ["original-envelope-id"] = "Original-Envelope-ID", + ["original-recipient"] = "Original-Recipient", + ["pragma"] = "Pragma", + ["proxy-authenticate"] = "Proxy-Authenticate", + ["proxy-authorization"] = "Proxy-Authorization", + ["range"] = "Range", + ["received"] = "Received", + ["received-from-mta"] = "Received-From-MTA", + ["references"] = "References", + ["referer"] = "Referer", + ["remote-mta"] = "Remote-MTA", + ["reply-to"] = "Reply-To", + ["reporting-mta"] = "Reporting-MTA", + ["resent-bcc"] = "Resent-Bcc", + ["resent-cc"] = "Resent-Cc", + ["resent-date"] = "Resent-Date", + ["resent-from"] = "Resent-From", + ["resent-message-id"] = "Resent-Message-ID", + ["resent-reply-to"] = "Resent-Reply-To", + ["resent-sender"] = "Resent-Sender", + ["resent-to"] = "Resent-To", + ["retry-after"] = "Retry-After", + ["return-path"] = "Return-Path", + ["sender"] = "Sender", + ["server"] = "Server", + ["smtp-remote-recipient"] = "SMTP-Remote-Recipient", + ["status"] = "Status", + ["subject"] = "Subject", + ["te"] = "TE", + ["to"] = "To", + ["trailer"] = "Trailer", + ["transfer-encoding"] = "Transfer-Encoding", + ["upgrade"] = "Upgrade", + ["user-agent"] = "User-Agent", + ["vary"] = "Vary", + ["via"] = "Via", + ["warning"] = "Warning", + ["will-retry-until"] = "Will-Retry-Until", + ["www-authenticate"] = "WWW-Authenticate", + ["x-mailer"] = "X-Mailer", +} + +return _M \ No newline at end of file diff --git a/Me_Lua/r13/socket/http.lua b/Me_Lua/r13/socket/http.lua new file mode 100644 index 0000000..4f8c1c8 --- /dev/null +++ b/Me_Lua/r13/socket/http.lua @@ -0,0 +1,356 @@ +----------------------------------------------------------------------------- +-- HTTP/1.1 client support for the Lua language. +-- LuaSocket toolkit. +-- Author: Diego Nehab +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Declare module and import dependencies +------------------------------------------------------------------------------- +local socket = require("socket") +local url = require("socket.url") +local ltn12 = require("ltn12") +local mime = require("mime") +local string = require("string") +local headers = require("socket.headers") +local base = _G +local table = require("table") +socket.http = {} +local _M = socket.http + +----------------------------------------------------------------------------- +-- Program constants +----------------------------------------------------------------------------- +-- connection timeout in seconds +_M.TIMEOUT = 60 +-- default port for document retrieval +_M.PORT = 80 +-- user agent field sent in request +_M.USERAGENT = socket._VERSION + +----------------------------------------------------------------------------- +-- Reads MIME headers from a connection, unfolding where needed +----------------------------------------------------------------------------- +local function receiveheaders(sock, headers) + local line, name, value, err + headers = headers or {} + -- get first line + line, err = sock:receive() + if err then return nil, err end + -- headers go until a blank line is found + while line ~= "" do + -- get field-name and value + name, value = socket.skip(2, string.find(line, "^(.-):%s*(.*)")) + if not (name and value) then return nil, "malformed reponse headers" end + name = string.lower(name) + -- get next line (value might be folded) + line, err = sock:receive() + if err then return nil, err end + -- unfold any folded values + while string.find(line, "^%s") do + value = value .. line + line = sock:receive() + if err then return nil, err end + end + -- save pair in table + if headers[name] then headers[name] = headers[name] .. ", " .. value + else headers[name] = value end + end + return headers +end + +----------------------------------------------------------------------------- +-- Extra sources and sinks +----------------------------------------------------------------------------- +socket.sourcet["http-chunked"] = function(sock, headers) + return base.setmetatable({ + getfd = function() return sock:getfd() end, + dirty = function() return sock:dirty() end + }, { + __call = function() + -- get chunk size, skip extention + local line, err = sock:receive() + if err then return nil, err end + local size = base.tonumber(string.gsub(line, ";.*", ""), 16) + if not size then return nil, "invalid chunk size" end + -- was it the last chunk? + if size > 0 then + -- if not, get chunk and skip terminating CRLF + local chunk, err, part = sock:receive(size) + if chunk then sock:receive() end + return chunk, err + else + -- if it was, read trailers into headers table + headers, err = receiveheaders(sock, headers) + if not headers then return nil, err end + end + end + }) +end + +socket.sinkt["http-chunked"] = function(sock) + return base.setmetatable({ + getfd = function() return sock:getfd() end, + dirty = function() return sock:dirty() end + }, { + __call = function(self, chunk, err) + if not chunk then return sock:send("0\r\n\r\n") end + local size = string.format("%X\r\n", string.len(chunk)) + return sock:send(size .. chunk .. "\r\n") + end + }) +end + +----------------------------------------------------------------------------- +-- Low level HTTP API +----------------------------------------------------------------------------- +local metat = { __index = {} } + +function _M.open(host, port, create) + -- create socket with user connect function, or with default + local c = socket.try((create or socket.tcp)()) + local h = base.setmetatable({ c = c }, metat) + -- create finalized try + h.try = socket.newtry(function() h:close() end) + -- set timeout before connecting + h.try(c:settimeout(_M.TIMEOUT)) + h.try(c:connect(host, port or _M.PORT)) + -- here everything worked + return h +end + +function metat.__index:sendrequestline(method, uri) + local reqline = string.format("%s %s HTTP/1.1\r\n", method or "GET", uri) + return self.try(self.c:send(reqline)) +end + +function metat.__index:sendheaders(tosend) + local canonic = headers.canonic + local h = "\r\n" + for f, v in base.pairs(tosend) do + h = (canonic[f] or f) .. ": " .. v .. "\r\n" .. h + end + self.try(self.c:send(h)) + return 1 +end + +function metat.__index:sendbody(headers, source, step) + source = source or ltn12.source.empty() + step = step or ltn12.pump.step + -- if we don't know the size in advance, send chunked and hope for the best + local mode = "http-chunked" + if headers["content-length"] then mode = "keep-open" end + return self.try(ltn12.pump.all(source, socket.sink(mode, self.c), step)) +end + +function metat.__index:receivestatusline() + local status = self.try(self.c:receive(5)) + -- identify HTTP/0.9 responses, which do not contain a status line + -- this is just a heuristic, but is what the RFC recommends + if status ~= "HTTP/" then return nil, status end + -- otherwise proceed reading a status line + status = self.try(self.c:receive("*l", status)) + local code = socket.skip(2, string.find(status, "HTTP/%d*%.%d* (%d%d%d)")) + return self.try(base.tonumber(code), status) +end + +function metat.__index:receiveheaders() + return self.try(receiveheaders(self.c)) +end + +function metat.__index:receivebody(headers, sink, step) + sink = sink or ltn12.sink.null() + step = step or ltn12.pump.step + local length = base.tonumber(headers["content-length"]) + local t = headers["transfer-encoding"] -- shortcut + local mode = "default" -- connection close + if t and t ~= "identity" then mode = "http-chunked" + elseif base.tonumber(headers["content-length"]) then mode = "by-length" end + return self.try(ltn12.pump.all(socket.source(mode, self.c, length), + sink, step)) +end + +function metat.__index:receive09body(status, sink, step) + local source = ltn12.source.rewind(socket.source("until-closed", self.c)) + source(status) + return self.try(ltn12.pump.all(source, sink, step)) +end + +function metat.__index:close() + return self.c:close() +end + +----------------------------------------------------------------------------- +-- High level HTTP API +----------------------------------------------------------------------------- +local function adjusturi(reqt) + local u = reqt + -- if there is a proxy, we need the full url. otherwise, just a part. + if not reqt.proxy and not _M.PROXY then + u = { + path = socket.try(reqt.path, "invalid path 'nil'"), + params = reqt.params, + query = reqt.query, + fragment = reqt.fragment + } + end + return url.build(u) +end + +local function adjustproxy(reqt) + local proxy = reqt.proxy or _M.PROXY + if proxy then + proxy = url.parse(proxy) + return proxy.host, proxy.port or 3128 + else + return reqt.host, reqt.port + end +end + +local function adjustheaders(reqt) + -- default headers + local host = reqt.host + if reqt.port then host = host .. ":" .. reqt.port end + local lower = { + ["user-agent"] = _M.USERAGENT, + ["host"] = host, + ["connection"] = "close, TE", + ["te"] = "trailers" + } + -- if we have authentication information, pass it along + if reqt.user and reqt.password then + lower["authorization"] = + "Basic " .. (mime.b64(reqt.user .. ":" .. reqt.password)) + end + -- override with user headers + for i,v in base.pairs(reqt.headers or lower) do + lower[string.lower(i)] = v + end + return lower +end + +-- default url parts +local default = { + host = "", + port = _M.PORT, + path ="/", + scheme = "http" +} + +local function adjustrequest(reqt) + -- parse url if provided + local nreqt = reqt.url and url.parse(reqt.url, default) or {} + -- explicit components override url + for i,v in base.pairs(reqt) do nreqt[i] = v end + if nreqt.port == "" then nreqt.port = 80 end + socket.try(nreqt.host and nreqt.host ~= "", + "invalid host '" .. base.tostring(nreqt.host) .. "'") + -- compute uri if user hasn't overriden + nreqt.uri = reqt.uri or adjusturi(nreqt) + -- ajust host and port if there is a proxy + nreqt.host, nreqt.port = adjustproxy(nreqt) + -- adjust headers in request + nreqt.headers = adjustheaders(nreqt) + return nreqt +end + +local function shouldredirect(reqt, code, headers) + return headers.location and + string.gsub(headers.location, "%s", "") ~= "" and + (reqt.redirect ~= false) and + (code == 301 or code == 302 or code == 303 or code == 307) and + (not reqt.method or reqt.method == "GET" or reqt.method == "HEAD") + and (not reqt.nredirects or reqt.nredirects < 5) +end + +local function shouldreceivebody(reqt, code) + if reqt.method == "HEAD" then return nil end + if code == 204 or code == 304 then return nil end + if code >= 100 and code < 200 then return nil end + return 1 +end + +-- forward declarations +local trequest, tredirect + +--[[local]] function tredirect(reqt, location) + local result, code, headers, status = trequest { + -- the RFC says the redirect URL has to be absolute, but some + -- servers do not respect that + url = url.absolute(reqt.url, location), + source = reqt.source, + sink = reqt.sink, + headers = reqt.headers, + proxy = reqt.proxy, + nredirects = (reqt.nredirects or 0) + 1, + create = reqt.create + } + -- pass location header back as a hint we redirected + headers = headers or {} + headers.location = headers.location or location + return result, code, headers, status +end + +--[[local]] function trequest(reqt) + -- we loop until we get what we want, or + -- until we are sure there is no way to get it + local nreqt = adjustrequest(reqt) + local h = _M.open(nreqt.host, nreqt.port, nreqt.create) + -- send request line and headers + h:sendrequestline(nreqt.method, nreqt.uri) + h:sendheaders(nreqt.headers) + -- if there is a body, send it + if nreqt.source then + h:sendbody(nreqt.headers, nreqt.source, nreqt.step) + end + local code, status = h:receivestatusline() + -- if it is an HTTP/0.9 server, simply get the body and we are done + if not code then + h:receive09body(status, nreqt.sink, nreqt.step) + return 1, 200 + end + local headers + -- ignore any 100-continue messages + while code == 100 do + headers = h:receiveheaders() + code, status = h:receivestatusline() + end + headers = h:receiveheaders() + -- at this point we should have a honest reply from the server + -- we can't redirect if we already used the source, so we report the error + if shouldredirect(nreqt, code, headers) and not nreqt.source then + h:close() + return tredirect(reqt, headers.location) + end + -- here we are finally done + if shouldreceivebody(nreqt, code) then + h:receivebody(headers, nreqt.sink, nreqt.step) + end + h:close() + return 1, code, headers, status +end + +local function srequest(u, b) + local t = {} + local reqt = { + url = u, + sink = ltn12.sink.table(t) + } + if b then + reqt.source = ltn12.source.string(b) + reqt.headers = { + ["content-length"] = string.len(b), + ["content-type"] = "application/x-www-form-urlencoded" + } + reqt.method = "POST" + end + local code, headers, status = socket.skip(1, trequest(reqt)) + return table.concat(t), code, headers, status +end + +_M.request = socket.protect(function(reqt, body) + if base.type(reqt) == "string" then return srequest(reqt, body) + else return trequest(reqt) end +end) + +return _M \ No newline at end of file diff --git a/Me_Lua/r13/socket/smtp.lua b/Me_Lua/r13/socket/smtp.lua new file mode 100644 index 0000000..b113d00 --- /dev/null +++ b/Me_Lua/r13/socket/smtp.lua @@ -0,0 +1,256 @@ +----------------------------------------------------------------------------- +-- SMTP client support for the Lua language. +-- LuaSocket toolkit. +-- Author: Diego Nehab +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Declare module and import dependencies +----------------------------------------------------------------------------- +local base = _G +local coroutine = require("coroutine") +local string = require("string") +local math = require("math") +local os = require("os") +local socket = require("socket") +local tp = require("socket.tp") +local ltn12 = require("ltn12") +local headers = require("socket.headers") +local mime = require("mime") + +socket.smtp = {} +local _M = socket.smtp + +----------------------------------------------------------------------------- +-- Program constants +----------------------------------------------------------------------------- +-- timeout for connection +_M.TIMEOUT = 60 +-- default server used to send e-mails +_M.SERVER = "localhost" +-- default port +_M.PORT = 25 +-- domain used in HELO command and default sendmail +-- If we are under a CGI, try to get from environment +_M.DOMAIN = os.getenv("SERVER_NAME") or "localhost" +-- default time zone (means we don't know) +_M.ZONE = "-0000" + +--------------------------------------------------------------------------- +-- Low level SMTP API +----------------------------------------------------------------------------- +local metat = { __index = {} } + +function metat.__index:greet(domain) + self.try(self.tp:check("2..")) + self.try(self.tp:command("EHLO", domain or _M.DOMAIN)) + return socket.skip(1, self.try(self.tp:check("2.."))) +end + +function metat.__index:mail(from) + self.try(self.tp:command("MAIL", "FROM:" .. from)) + return self.try(self.tp:check("2..")) +end + +function metat.__index:rcpt(to) + self.try(self.tp:command("RCPT", "TO:" .. to)) + return self.try(self.tp:check("2..")) +end + +function metat.__index:data(src, step) + self.try(self.tp:command("DATA")) + self.try(self.tp:check("3..")) + self.try(self.tp:source(src, step)) + self.try(self.tp:send("\r\n.\r\n")) + return self.try(self.tp:check("2..")) +end + +function metat.__index:quit() + self.try(self.tp:command("QUIT")) + return self.try(self.tp:check("2..")) +end + +function metat.__index:close() + return self.tp:close() +end + +function metat.__index:login(user, password) + self.try(self.tp:command("AUTH", "LOGIN")) + self.try(self.tp:check("3..")) + self.try(self.tp:send(mime.b64(user) .. "\r\n")) + self.try(self.tp:check("3..")) + self.try(self.tp:send(mime.b64(password) .. "\r\n")) + return self.try(self.tp:check("2..")) +end + +function metat.__index:plain(user, password) + local auth = "PLAIN " .. mime.b64("\0" .. user .. "\0" .. password) + self.try(self.tp:command("AUTH", auth)) + return self.try(self.tp:check("2..")) +end + +function metat.__index:auth(user, password, ext) + if not user or not password then return 1 end + if string.find(ext, "AUTH[^\n]+LOGIN") then + return self:login(user, password) + elseif string.find(ext, "AUTH[^\n]+PLAIN") then + return self:plain(user, password) + else + self.try(nil, "authentication not supported") + end +end + +-- send message or throw an exception +function metat.__index:send(mailt) + self:mail(mailt.from) + if base.type(mailt.rcpt) == "table" then + for i,v in base.ipairs(mailt.rcpt) do + self:rcpt(v) + end + else + self:rcpt(mailt.rcpt) + end + self:data(ltn12.source.chain(mailt.source, mime.stuff()), mailt.step) +end + +function _M.open(server, port, create) + local tp = socket.try(tp.connect(server or _M.SERVER, port or _M.PORT, + _M.TIMEOUT, create)) + local s = base.setmetatable({tp = tp}, metat) + -- make sure tp is closed if we get an exception + s.try = socket.newtry(function() + s:close() + end) + return s +end + +-- convert headers to lowercase +local function lower_headers(headers) + local lower = {} + for i,v in base.pairs(headers or lower) do + lower[string.lower(i)] = v + end + return lower +end + +--------------------------------------------------------------------------- +-- Multipart message source +----------------------------------------------------------------------------- +-- returns a hopefully unique mime boundary +local seqno = 0 +local function newboundary() + seqno = seqno + 1 + return string.format('%s%05d==%05u', os.date('%d%m%Y%H%M%S'), + math.random(0, 99999), seqno) +end + +-- send_message forward declaration +local send_message + +-- yield the headers all at once, it's faster +local function send_headers(tosend) + local canonic = headers.canonic + local h = "\r\n" + for f,v in base.pairs(tosend) do + h = (canonic[f] or f) .. ': ' .. v .. "\r\n" .. h + end + coroutine.yield(h) +end + +-- yield multipart message body from a multipart message table +local function send_multipart(mesgt) + -- make sure we have our boundary and send headers + local bd = newboundary() + local headers = lower_headers(mesgt.headers or {}) + headers['content-type'] = headers['content-type'] or 'multipart/mixed' + headers['content-type'] = headers['content-type'] .. + '; boundary="' .. bd .. '"' + send_headers(headers) + -- send preamble + if mesgt.body.preamble then + coroutine.yield(mesgt.body.preamble) + coroutine.yield("\r\n") + end + -- send each part separated by a boundary + for i, m in base.ipairs(mesgt.body) do + coroutine.yield("\r\n--" .. bd .. "\r\n") + send_message(m) + end + -- send last boundary + coroutine.yield("\r\n--" .. bd .. "--\r\n\r\n") + -- send epilogue + if mesgt.body.epilogue then + coroutine.yield(mesgt.body.epilogue) + coroutine.yield("\r\n") + end +end + +-- yield message body from a source +local function send_source(mesgt) + -- make sure we have a content-type + local headers = lower_headers(mesgt.headers or {}) + headers['content-type'] = headers['content-type'] or + 'text/plain; charset="iso-8859-1"' + send_headers(headers) + -- send body from source + while true do + local chunk, err = mesgt.body() + if err then coroutine.yield(nil, err) + elseif chunk then coroutine.yield(chunk) + else break end + end +end + +-- yield message body from a string +local function send_string(mesgt) + -- make sure we have a content-type + local headers = lower_headers(mesgt.headers or {}) + headers['content-type'] = headers['content-type'] or + 'text/plain; charset="iso-8859-1"' + send_headers(headers) + -- send body from string + coroutine.yield(mesgt.body) +end + +-- message source +function send_message(mesgt) + if base.type(mesgt.body) == "table" then send_multipart(mesgt) + elseif base.type(mesgt.body) == "function" then send_source(mesgt) + else send_string(mesgt) end +end + +-- set defaul headers +local function adjust_headers(mesgt) + local lower = lower_headers(mesgt.headers) + lower["date"] = lower["date"] or + os.date("!%a, %d %b %Y %H:%M:%S ") .. (mesgt.zone or _M.ZONE) + lower["x-mailer"] = lower["x-mailer"] or socket._VERSION + -- this can't be overriden + lower["mime-version"] = "1.0" + return lower +end + +function _M.message(mesgt) + mesgt.headers = adjust_headers(mesgt) + -- create and return message source + local co = coroutine.create(function() send_message(mesgt) end) + return function() + local ret, a, b = coroutine.resume(co) + if ret then return a, b + else return nil, a end + end +end + +--------------------------------------------------------------------------- +-- High level SMTP API +----------------------------------------------------------------------------- +_M.send = socket.protect(function(mailt) + local s = _M.open(mailt.server, mailt.port, mailt.create) + local ext = s:greet(mailt.domain) + s:auth(mailt.user, mailt.password, ext) + s:send(mailt) + s:quit() + return s:close() +end) + +return _M \ No newline at end of file diff --git a/Me_Lua/r13/socket/tp.lua b/Me_Lua/r13/socket/tp.lua new file mode 100644 index 0000000..cbeff56 --- /dev/null +++ b/Me_Lua/r13/socket/tp.lua @@ -0,0 +1,126 @@ +----------------------------------------------------------------------------- +-- Unified SMTP/FTP subsystem +-- LuaSocket toolkit. +-- Author: Diego Nehab +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Declare module and import dependencies +----------------------------------------------------------------------------- +local base = _G +local string = require("string") +local socket = require("socket") +local ltn12 = require("ltn12") + +socket.tp = {} +local _M = socket.tp + +----------------------------------------------------------------------------- +-- Program constants +----------------------------------------------------------------------------- +_M.TIMEOUT = 60 + +----------------------------------------------------------------------------- +-- Implementation +----------------------------------------------------------------------------- +-- gets server reply (works for SMTP and FTP) +local function get_reply(c) + local code, current, sep + local line, err = c:receive() + local reply = line + if err then return nil, err end + code, sep = socket.skip(2, string.find(line, "^(%d%d%d)(.?)")) + if not code then return nil, "invalid server reply" end + if sep == "-" then -- reply is multiline + repeat + line, err = c:receive() + if err then return nil, err end + current, sep = socket.skip(2, string.find(line, "^(%d%d%d)(.?)")) + reply = reply .. "\n" .. line + -- reply ends with same code + until code == current and sep == " " + end + return code, reply +end + +-- metatable for sock object +local metat = { __index = {} } + +function metat.__index:check(ok) + local code, reply = get_reply(self.c) + if not code then return nil, reply end + if base.type(ok) ~= "function" then + if base.type(ok) == "table" then + for i, v in base.ipairs(ok) do + if string.find(code, v) then + return base.tonumber(code), reply + end + end + return nil, reply + else + if string.find(code, ok) then return base.tonumber(code), reply + else return nil, reply end + end + else return ok(base.tonumber(code), reply) end +end + +function metat.__index:command(cmd, arg) + cmd = string.upper(cmd) + if arg then + return self.c:send(cmd .. " " .. arg.. "\r\n") + else + return self.c:send(cmd .. "\r\n") + end +end + +function metat.__index:sink(snk, pat) + local chunk, err = c:receive(pat) + return snk(chunk, err) +end + +function metat.__index:send(data) + return self.c:send(data) +end + +function metat.__index:receive(pat) + return self.c:receive(pat) +end + +function metat.__index:getfd() + return self.c:getfd() +end + +function metat.__index:dirty() + return self.c:dirty() +end + +function metat.__index:getcontrol() + return self.c +end + +function metat.__index:source(source, step) + local sink = socket.sink("keep-open", self.c) + local ret, err = ltn12.pump.all(source, sink, step or ltn12.pump.step) + return ret, err +end + +-- closes the underlying c +function metat.__index:close() + self.c:close() + return 1 +end + +-- connect with server and return c object +function _M.connect(host, port, timeout, create) + local c, e = (create or socket.tcp)() + if not c then return nil, e end + c:settimeout(timeout or _M.TIMEOUT) + local r, e = c:connect(host, port) + if not r then + c:close() + return nil, e + end + return base.setmetatable({c = c}, metat) +end + +return _M \ No newline at end of file diff --git a/Me_Lua/r13/socket/url.lua b/Me_Lua/r13/socket/url.lua new file mode 100644 index 0000000..92ba7fd --- /dev/null +++ b/Me_Lua/r13/socket/url.lua @@ -0,0 +1,307 @@ +----------------------------------------------------------------------------- +-- URI parsing, composition and relative URL resolution +-- LuaSocket toolkit. +-- Author: Diego Nehab +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Declare module +----------------------------------------------------------------------------- +local string = require("string") +local base = _G +local table = require("table") +local socket = require("socket") + +socket.url = {} +local _M = socket.url + +----------------------------------------------------------------------------- +-- Module version +----------------------------------------------------------------------------- +_M._VERSION = "URL 1.0.3" + +----------------------------------------------------------------------------- +-- Encodes a string into its escaped hexadecimal representation +-- Input +-- s: binary string to be encoded +-- Returns +-- escaped representation of string binary +----------------------------------------------------------------------------- +function _M.escape(s) + return (string.gsub(s, "([^A-Za-z0-9_])", function(c) + return string.format("%%%02x", string.byte(c)) + end)) +end + +----------------------------------------------------------------------------- +-- Protects a path segment, to prevent it from interfering with the +-- url parsing. +-- Input +-- s: binary string to be encoded +-- Returns +-- escaped representation of string binary +----------------------------------------------------------------------------- +local function make_set(t) + local s = {} + for i,v in base.ipairs(t) do + s[t[i]] = 1 + end + return s +end + +-- these are allowed withing a path segment, along with alphanum +-- other characters must be escaped +local segment_set = make_set { + "-", "_", ".", "!", "~", "*", "'", "(", + ")", ":", "@", "&", "=", "+", "$", ",", +} + +local function protect_segment(s) + return string.gsub(s, "([^A-Za-z0-9_])", function (c) + if segment_set[c] then return c + else return string.format("%%%02x", string.byte(c)) end + end) +end + +----------------------------------------------------------------------------- +-- Encodes a string into its escaped hexadecimal representation +-- Input +-- s: binary string to be encoded +-- Returns +-- escaped representation of string binary +----------------------------------------------------------------------------- +function _M.unescape(s) + return (string.gsub(s, "%%(%x%x)", function(hex) + return string.char(base.tonumber(hex, 16)) + end)) +end + +----------------------------------------------------------------------------- +-- Builds a path from a base path and a relative path +-- Input +-- base_path +-- relative_path +-- Returns +-- corresponding absolute path +----------------------------------------------------------------------------- +local function absolute_path(base_path, relative_path) + if string.sub(relative_path, 1, 1) == "/" then return relative_path end + local path = string.gsub(base_path, "[^/]*$", "") + path = path .. relative_path + path = string.gsub(path, "([^/]*%./)", function (s) + if s ~= "./" then return s else return "" end + end) + path = string.gsub(path, "/%.$", "/") + local reduced + while reduced ~= path do + reduced = path + path = string.gsub(reduced, "([^/]*/%.%./)", function (s) + if s ~= "../../" then return "" else return s end + end) + end + path = string.gsub(reduced, "([^/]*/%.%.)$", function (s) + if s ~= "../.." then return "" else return s end + end) + return path +end + +----------------------------------------------------------------------------- +-- Parses a url and returns a table with all its parts according to RFC 2396 +-- The following grammar describes the names given to the URL parts +-- ::= :///;?# +-- ::= @: +-- ::= [:] +-- :: = {/} +-- Input +-- url: uniform resource locator of request +-- default: table with default values for each field +-- Returns +-- table with the following fields, where RFC naming conventions have +-- been preserved: +-- scheme, authority, userinfo, user, password, host, port, +-- path, params, query, fragment +-- Obs: +-- the leading '/' in {/} is considered part of +----------------------------------------------------------------------------- +function _M.parse(url, default) + -- initialize default parameters + local parsed = {} + for i,v in base.pairs(default or parsed) do parsed[i] = v end + -- empty url is parsed to nil + if not url or url == "" then return nil, "invalid url" end + -- remove whitespace + -- url = string.gsub(url, "%s", "") + -- get fragment + url = string.gsub(url, "#(.*)$", function(f) + parsed.fragment = f + return "" + end) + -- get scheme + url = string.gsub(url, "^([%w][%w%+%-%.]*)%:", + function(s) parsed.scheme = s; return "" end) + -- get authority + url = string.gsub(url, "^//([^/]*)", function(n) + parsed.authority = n + return "" + end) + -- get query string + url = string.gsub(url, "%?(.*)", function(q) + parsed.query = q + return "" + end) + -- get params + url = string.gsub(url, "%;(.*)", function(p) + parsed.params = p + return "" + end) + -- path is whatever was left + if url ~= "" then parsed.path = url end + local authority = parsed.authority + if not authority then return parsed end + authority = string.gsub(authority,"^([^@]*)@", + function(u) parsed.userinfo = u; return "" end) + authority = string.gsub(authority, ":([^:%]]*)$", + function(p) parsed.port = p; return "" end) + if authority ~= "" then + -- IPv6? + parsed.host = string.match(authority, "^%[(.+)%]$") or authority + end + local userinfo = parsed.userinfo + if not userinfo then return parsed end + userinfo = string.gsub(userinfo, ":([^:]*)$", + function(p) parsed.password = p; return "" end) + parsed.user = userinfo + return parsed +end + +----------------------------------------------------------------------------- +-- Rebuilds a parsed URL from its components. +-- Components are protected if any reserved or unallowed characters are found +-- Input +-- parsed: parsed URL, as returned by parse +-- Returns +-- a stringing with the corresponding URL +----------------------------------------------------------------------------- +function _M.build(parsed) + local ppath = _M.parse_path(parsed.path or "") + local url = _M.build_path(ppath) + if parsed.params then url = url .. ";" .. parsed.params end + if parsed.query then url = url .. "?" .. parsed.query end + local authority = parsed.authority + if parsed.host then + authority = parsed.host + if string.find(authority, ":") then -- IPv6? + authority = "[" .. authority .. "]" + end + if parsed.port then authority = authority .. ":" .. parsed.port end + local userinfo = parsed.userinfo + if parsed.user then + userinfo = parsed.user + if parsed.password then + userinfo = userinfo .. ":" .. parsed.password + end + end + if userinfo then authority = userinfo .. "@" .. authority end + end + if authority then url = "//" .. authority .. url end + if parsed.scheme then url = parsed.scheme .. ":" .. url end + if parsed.fragment then url = url .. "#" .. parsed.fragment end + -- url = string.gsub(url, "%s", "") + return url +end + +----------------------------------------------------------------------------- +-- Builds a absolute URL from a base and a relative URL according to RFC 2396 +-- Input +-- base_url +-- relative_url +-- Returns +-- corresponding absolute url +----------------------------------------------------------------------------- +function _M.absolute(base_url, relative_url) + if base.type(base_url) == "table" then + base_parsed = base_url + base_url = _M.build(base_parsed) + else + base_parsed = _M.parse(base_url) + end + local relative_parsed = _M.parse(relative_url) + if not base_parsed then return relative_url + elseif not relative_parsed then return base_url + elseif relative_parsed.scheme then return relative_url + else + relative_parsed.scheme = base_parsed.scheme + if not relative_parsed.authority then + relative_parsed.authority = base_parsed.authority + if not relative_parsed.path then + relative_parsed.path = base_parsed.path + if not relative_parsed.params then + relative_parsed.params = base_parsed.params + if not relative_parsed.query then + relative_parsed.query = base_parsed.query + end + end + else + relative_parsed.path = absolute_path(base_parsed.path or "", + relative_parsed.path) + end + end + return _M.build(relative_parsed) + end +end + +----------------------------------------------------------------------------- +-- Breaks a path into its segments, unescaping the segments +-- Input +-- path +-- Returns +-- segment: a table with one entry per segment +----------------------------------------------------------------------------- +function _M.parse_path(path) + local parsed = {} + path = path or "" + --path = string.gsub(path, "%s", "") + string.gsub(path, "([^/]+)", function (s) table.insert(parsed, s) end) + for i = 1, #parsed do + parsed[i] = _M.unescape(parsed[i]) + end + if string.sub(path, 1, 1) == "/" then parsed.is_absolute = 1 end + if string.sub(path, -1, -1) == "/" then parsed.is_directory = 1 end + return parsed +end + +----------------------------------------------------------------------------- +-- Builds a path component from its segments, escaping protected characters. +-- Input +-- parsed: path segments +-- unsafe: if true, segments are not protected before path is built +-- Returns +-- path: corresponding path stringing +----------------------------------------------------------------------------- +function _M.build_path(parsed, unsafe) + local path = "" + local n = #parsed + if unsafe then + for i = 1, n-1 do + path = path .. parsed[i] + path = path .. "/" + end + if n > 0 then + path = path .. parsed[n] + if parsed.is_directory then path = path .. "/" end + end + else + for i = 1, n-1 do + path = path .. protect_segment(parsed[i]) + path = path .. "/" + end + if n > 0 then + path = path .. protect_segment(parsed[n]) + if parsed.is_directory then path = path .. "/" end + end + end + if parsed.is_absolute then path = "/" .. path end + return path +end + +return _M diff --git a/Me_Lua/r13/ssl.lua b/Me_Lua/r13/ssl.lua new file mode 100644 index 0000000..1cf2e39 --- /dev/null +++ b/Me_Lua/r13/ssl.lua @@ -0,0 +1,168 @@ +------------------------------------------------------------------------------ +-- LuaSec 0.5 +-- Copyright (C) 2006-2014 Bruno Silvestre +-- +------------------------------------------------------------------------------ + +local core = require("ssl.core") +local context = require("ssl.context") +local x509 = require("ssl.x509") + +module("ssl", package.seeall) + +_VERSION = "0.5.PR" +_COPYRIGHT = core.copyright() + +-- Export +loadcertificate = x509.load + +-- We must prevent the contexts to be collected before the connections, +-- otherwise the C registry will be cleared. +local registry = setmetatable({}, {__mode="k"}) + +-- +-- +-- +local function optexec(func, param, ctx) + if param then + if type(param) == "table" then + return func(ctx, unpack(param)) + else + return func(ctx, param) + end + end + return true +end + +-- +-- +-- +function newcontext(cfg) + local succ, msg, ctx + -- Create the context + ctx, msg = context.create(cfg.protocol) + if not ctx then return nil, msg end + -- Mode + succ, msg = context.setmode(ctx, cfg.mode) + if not succ then return nil, msg end + -- Load the key + if cfg.key then + if cfg.password and + type(cfg.password) ~= "function" and + type(cfg.password) ~= "string" + then + return nil, "invalid password type" + end + succ, msg = context.loadkey(ctx, cfg.key, cfg.password) + if not succ then return nil, msg end + end + -- Load the certificate + if cfg.certificate then + succ, msg = context.loadcert(ctx, cfg.certificate) + if not succ then return nil, msg end + end + -- Load the CA certificates + if cfg.cafile or cfg.capath then + succ, msg = context.locations(ctx, cfg.cafile, cfg.capath) + if not succ then return nil, msg end + end + -- Set SSL ciphers + if cfg.ciphers then + succ, msg = context.setcipher(ctx, cfg.ciphers) + if not succ then return nil, msg end + end + -- Set the verification options + succ, msg = optexec(context.setverify, cfg.verify, ctx) + if not succ then return nil, msg end + -- Set SSL options + succ, msg = optexec(context.setoptions, cfg.options, ctx) + if not succ then return nil, msg end + -- Set the depth for certificate verification + if cfg.depth then + succ, msg = context.setdepth(ctx, cfg.depth) + if not succ then return nil, msg end + end + + -- NOTE: Setting DH parameters and elliptic curves needs to come after + -- setoptions(), in case the user has specified the single_{dh,ecdh}_use + -- options. + + -- Set DH parameters + if cfg.dhparam then + if type(cfg.dhparam) ~= "function" then + return nil, "invalid DH parameter type" + end + context.setdhparam(ctx, cfg.dhparam) + end + -- Set elliptic curve + if cfg.curve then + succ, msg = context.setcurve(ctx, cfg.curve) + if not succ then return nil, msg end + end + -- Set extra verification options + if cfg.verifyext and ctx.setverifyext then + succ, msg = optexec(ctx.setverifyext, cfg.verifyext, ctx) + if not succ then return nil, msg end + end + + return ctx +end + +-- +-- +-- +function wrap(sock, cfg) + local ctx, msg + if type(cfg) == "table" then + ctx, msg = newcontext(cfg) + if not ctx then return nil, msg end + else + ctx = cfg + end + local s, msg = core.create(ctx) + if s then + core.setfd(s, sock:getfd()) + sock:setfd(core.invalidfd) + registry[s] = ctx + return s + end + return nil, msg +end + +-- +-- Extract connection information. +-- +local function info(ssl, field) + local str, comp, err, protocol + comp, err = core.compression(ssl) + if err then + return comp, err + end + -- Avoid parser + if field == "compression" then + return comp + end + local info = {compression = comp} + str, info.bits, info.algbits, protocol = core.info(ssl) + if str then + info.cipher, info.protocol, info.key, + info.authentication, info.encryption, info.mac = + string.match(str, + "^(%S+)%s+(%S+)%s+Kx=(%S+)%s+Au=(%S+)%s+Enc=(%S+)%s+Mac=(%S+)") + info.export = (string.match(str, "%sexport%s*$") ~= nil) + end + if protocol then + info.protocol = protocol + end + if field then + return info[field] + end + -- Empty? + return ( (next(info)) and info ) +end + +-- +-- Set method for SSL connections. +-- +core.setmethod("info", info) + diff --git a/Me_Lua/r13/ssl.so b/Me_Lua/r13/ssl.so new file mode 100644 index 0000000000000000000000000000000000000000..40b37c57d73309f9c16cdcb45996f671c7f7d952 GIT binary patch literal 67881 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 xrN|v*U 1000000 then + sec = sec + 1 + usec = usec - 1000000 + end + + return { sec = sec, usec = usec } +end + + +function M.timercmp (x, y) + local x = { sec = x.sec or 0, usec = x.usec or 0 } + local y = { sec = y.sec or 0, usec = y.usec or 0 } + if x.sec ~= y.sec then + return x.sec - y.sec + else + return x.usec - y.usec + end +end + + +function M.timersub (x,y) + local sec, usec = 0, 0 + if x.sec then sec = x.sec end + if y.sec then sec = sec - y.sec end + if x.usec then usec = x.usec end + if y.usec then usec = usec - y.usec end + if usec < 0 then + sec = sec - 1 + usec = usec + 1000000 + end + + return { sec = sec, usec = usec } +end + +function M.timesleep (x) + local sec, nsec = 0, 0 + y = M.gettimeofday(); + if( M.timercmp(x, y) > 0 ) then + sec = x.sec - y.sec + nsec = (x.usec - y.usec) * 1000 + if nsec < 0 then + sec = sec - 1 + nsec = nsec + 1000000000 + end + M.nanosleep(sec, nsec) + end +end + +function M.strsplit(str, delim, maxNb) + -- Eliminate bad cases... + if string.find(str, delim) == nil then + return { str } + end + if maxNb == nil or maxNb < 1 then + maxNb = 0 -- No limit + end + local result = {} + local pat = "(.-)" .. delim .. "()" + local nb = 0 + local lastPos + for part, pos in string.gfind(str, pat) do + nb = nb + 1 + result[nb] = part + lastPos = pos + if nb == maxNb then break end + end + -- Handle the last field + if nb ~= maxNb then + result[nb + 1] = string.sub(str, lastPos) + end + return result +end + +function M.var_dump(data, max_level, prefix) + if type(prefix) ~= "string" then + prefix = "" + end + if type(data) ~= "table" then + print(prefix .. tostring(data)) + else + print(data) + if max_level ~= 0 then + local prefix_next = prefix .. " " + print(prefix .. "{") + for k,v in pairs(data) do + io.stdout:write(prefix_next .. k .. " = ") + if type(v) ~= "table" or (type(max_level) == "number" and max_level <= 1) then + print(v) + else + if max_level == nil then + M.var_dump(v, nil, prefix_next) + else + M.var_dump(v, max_level - 1, prefix_next) + end + end + end + print(prefix .. "}") + end + end +end + + +return M diff --git a/Mi_Lua/bit.lua b/Mi_Lua/bit.lua new file mode 100644 index 0000000..cdfecb1 --- /dev/null +++ b/Mi_Lua/bit.lua @@ -0,0 +1,15 @@ +--[[ +nixio - Linux I/O library for lua + +Copyright 2009 Steven Barth + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +$Id$ +]]-- + +return require "nixio".bit \ No newline at end of file diff --git a/Mi_Lua/cjson.so b/Mi_Lua/cjson.so new file mode 100644 index 0000000000000000000000000000000000000000..723d8a2d41622c25978eb181ba60702fa24970c3 GIT binary patch literal 19332 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 NDfJ$96JWRA{{}29;RpZ# literal 0 HcmV?d00001 diff --git a/Mi_Lua/iproute.so b/Mi_Lua/iproute.so new file mode 100644 index 0000000000000000000000000000000000000000..43b0c9b9dc28a434bd8bd33d30fa2292616acbd4 GIT binary patch literal 23408 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 literal 0 HcmV?d00001 diff --git a/Mi_Lua/json.lua b/Mi_Lua/json.lua new file mode 100644 index 0000000..0e61274 --- /dev/null +++ b/Mi_Lua/json.lua @@ -0,0 +1,376 @@ +----------------------------------------------------------------------------- +-- JSON4Lua: JSON encoding / decoding support for the Lua language. +-- json Module. +-- Author: Craig Mason-Jones +-- Homepage: http://json.luaforge.net/ +-- Version: 0.9.40 +-- This module is released under the MIT License (MIT). +-- Please see LICENCE.txt for details. +-- +-- USAGE: +-- This module exposes two functions: +-- encode(o) +-- Returns the table / string / boolean / number / nil / json.null value as a JSON-encoded string. +-- decode(json_string) +-- Returns a Lua object populated with the data encoded in the JSON string json_string. +-- +-- REQUIREMENTS: +-- compat-5.1 if using Lua 5.0 +-- +-- CHANGELOG +-- 0.9.20 Introduction of local Lua functions for private functions (removed _ function prefix). +-- Fixed Lua 5.1 compatibility issues. +-- Introduced json.null to have null values in associative arrays. +-- encode() performance improvement (more than 50%) through table.concat rather than .. +-- Introduced decode ability to ignore /**/ comments in the JSON string. +-- 0.9.10 Fix to array encoding / decoding to correctly manage nil/null values in arrays. +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Imports and dependencies +----------------------------------------------------------------------------- +local math = require('math') +local string = require("string") +local table = require("table") + +local base = _G + +----------------------------------------------------------------------------- +-- Module declaration +----------------------------------------------------------------------------- +module("json") + +-- Public functions + +-- Private functions +local decode_scanArray +local decode_scanComment +local decode_scanConstant +local decode_scanNumber +local decode_scanObject +local decode_scanString +local decode_scanWhitespace +local encodeString +local isArray +local isEncodable + +----------------------------------------------------------------------------- +-- PUBLIC FUNCTIONS +----------------------------------------------------------------------------- +--- Encodes an arbitrary Lua object / variable. +-- @param v The Lua object / variable to be JSON encoded. +-- @return String containing the JSON encoding in internal Lua string format (i.e. not unicode) +function encode (v) + -- Handle nil values + if v==nil then + return "null" + end + + local vtype = base.type(v) + + -- Handle strings + if vtype=='string' then + return '"' .. encodeString(v) .. '"' -- Need to handle encoding in string + end + + -- Handle booleans + if vtype=='number' or vtype=='boolean' then + return base.tostring(v) + end + + -- Handle tables + if vtype=='table' then + local rval = {} + -- Consider arrays separately + local bArray, maxCount = isArray(v) + if bArray then + for i = 1,maxCount do + table.insert(rval, encode(v[i])) + end + else -- An object, not an array + for i,j in base.pairs(v) do + if isEncodable(i) and isEncodable(j) then + table.insert(rval, '"' .. encodeString(i) .. '":' .. encode(j)) + end + end + end + if bArray then + return '[' .. table.concat(rval,',') ..']' + else + return '{' .. table.concat(rval,',') .. '}' + end + end + + -- Handle null values + if vtype=='function' and v==null then + return 'null' + end + + base.assert(false,'encode attempt to encode unsupported type ' .. vtype .. ':' .. base.tostring(v)) +end + + +--- Decodes a JSON string and returns the decoded value as a Lua data structure / value. +-- @param s The string to scan. +-- @param [startPos] Optional starting position where the JSON string is located. Defaults to 1. +-- @param Lua object, number The object that was scanned, as a Lua table / string / number / boolean or nil, +-- and the position of the first character after +-- the scanned JSON object. +function decode(s, startPos) + startPos = startPos and startPos or 1 + startPos = decode_scanWhitespace(s,startPos) + base.assert(startPos<=string.len(s), 'Unterminated JSON encoded object found at position in [' .. s .. ']') + local curChar = string.sub(s,startPos,startPos) + -- Object + if curChar=='{' then + return decode_scanObject(s,startPos) + end + -- Array + if curChar=='[' then + return decode_scanArray(s,startPos) + end + -- Number + if string.find("+-0123456789.e", curChar, 1, true) then + return decode_scanNumber(s,startPos) + end + -- String + if curChar==[["]] or curChar==[[']] then + return decode_scanString(s,startPos) + end + if string.sub(s,startPos,startPos+1)=='/*' then + return decode(s, decode_scanComment(s,startPos)) + end + -- Otherwise, it must be a constant + return decode_scanConstant(s,startPos) +end + +--- The null function allows one to specify a null value in an associative array (which is otherwise +-- discarded if you set the value with 'nil' in Lua. Simply set t = { first=json.null } +function null() + return null -- so json.null() will also return null ;-) +end +----------------------------------------------------------------------------- +-- Internal, PRIVATE functions. +-- Following a Python-like convention, I have prefixed all these 'PRIVATE' +-- functions with an underscore. +----------------------------------------------------------------------------- + +--- Scans an array from JSON into a Lua object +-- startPos begins at the start of the array. +-- Returns the array and the next starting position +-- @param s The string being scanned. +-- @param startPos The starting position for the scan. +-- @return table, int The scanned array as a table, and the position of the next character to scan. +function decode_scanArray(s,startPos) + local array = {} -- The return value + local stringLen = string.len(s) + base.assert(string.sub(s,startPos,startPos)=='[','decode_scanArray called but array does not start at position ' .. startPos .. ' in string:\n'..s ) + startPos = startPos + 1 + -- Infinite loop for array elements + repeat + startPos = decode_scanWhitespace(s,startPos) + base.assert(startPos<=stringLen,'JSON String ended unexpectedly scanning array.') + local curChar = string.sub(s,startPos,startPos) + if (curChar==']') then + return array, startPos+1 + end + if (curChar==',') then + startPos = decode_scanWhitespace(s,startPos+1) + end + base.assert(startPos<=stringLen, 'JSON String ended unexpectedly scanning array.') + object, startPos = decode(s,startPos) + table.insert(array,object) + until false +end + +--- Scans a comment and discards the comment. +-- Returns the position of the next character following the comment. +-- @param string s The JSON string to scan. +-- @param int startPos The starting position of the comment +function decode_scanComment(s, startPos) + base.assert( string.sub(s,startPos,startPos+1)=='/*', "decode_scanComment called but comment does not start at position " .. startPos) + local endPos = string.find(s,'*/',startPos+2) + base.assert(endPos~=nil, "Unterminated comment in string at " .. startPos) + return endPos+2 +end + +--- Scans for given constants: true, false or null +-- Returns the appropriate Lua type, and the position of the next character to read. +-- @param s The string being scanned. +-- @param startPos The position in the string at which to start scanning. +-- @return object, int The object (true, false or nil) and the position at which the next character should be +-- scanned. +function decode_scanConstant(s, startPos) + local consts = { ["true"] = true, ["false"] = false, ["null"] = nil } + local constNames = {"true","false","null"} + + for i,k in base.pairs(constNames) do + --print ("[" .. string.sub(s,startPos, startPos + string.len(k) -1) .."]", k) + if string.sub(s,startPos, startPos + string.len(k) -1 )==k then + return consts[k], startPos + string.len(k) + end + end + base.assert(nil, 'Failed to scan constant from string ' .. s .. ' at starting position ' .. startPos) +end + +--- Scans a number from the JSON encoded string. +-- (in fact, also is able to scan numeric +- eqns, which is not +-- in the JSON spec.) +-- Returns the number, and the position of the next character +-- after the number. +-- @param s The string being scanned. +-- @param startPos The position at which to start scanning. +-- @return number, int The extracted number and the position of the next character to scan. +function decode_scanNumber(s,startPos) + local endPos = startPos+1 + local stringLen = string.len(s) + local acceptableChars = "+-0123456789.e" + while (string.find(acceptableChars, string.sub(s,endPos,endPos), 1, true) + and endPos<=stringLen + ) do + endPos = endPos + 1 + end + local stringValue = 'return ' .. string.sub(s,startPos, endPos-1) + local stringEval = base.loadstring(stringValue) + base.assert(stringEval, 'Failed to scan number [ ' .. stringValue .. '] in JSON string at position ' .. startPos .. ' : ' .. endPos) + return stringEval(), endPos +end + +--- Scans a JSON object into a Lua object. +-- startPos begins at the start of the object. +-- Returns the object and the next starting position. +-- @param s The string being scanned. +-- @param startPos The starting position of the scan. +-- @return table, int The scanned object as a table and the position of the next character to scan. +function decode_scanObject(s,startPos) + local object = {} + local stringLen = string.len(s) + local key, value + base.assert(string.sub(s,startPos,startPos)=='{','decode_scanObject called but object does not start at position ' .. startPos .. ' in string:\n' .. s) + startPos = startPos + 1 + repeat + startPos = decode_scanWhitespace(s,startPos) + base.assert(startPos<=stringLen, 'JSON string ended unexpectedly while scanning object.') + local curChar = string.sub(s,startPos,startPos) + if (curChar=='}') then + return object,startPos+1 + end + if (curChar==',') then + startPos = decode_scanWhitespace(s,startPos+1) + end + base.assert(startPos<=stringLen, 'JSON string ended unexpectedly scanning object.') + -- Scan the key + key, startPos = decode(s,startPos) + base.assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key) + startPos = decode_scanWhitespace(s,startPos) + base.assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key) + base.assert(string.sub(s,startPos,startPos)==':','JSON object key-value assignment mal-formed at ' .. startPos) + startPos = decode_scanWhitespace(s,startPos+1) + base.assert(startPos<=stringLen, 'JSON string ended unexpectedly searching for value of key ' .. key) + value, startPos = decode(s,startPos) + object[key]=value + until false -- infinite loop while key-value pairs are found +end + +--- Scans a JSON string from the opening inverted comma or single quote to the +-- end of the string. +-- Returns the string extracted as a Lua string, +-- and the position of the next non-string character +-- (after the closing inverted comma or single quote). +-- @param s The string being scanned. +-- @param startPos The starting position of the scan. +-- @return string, int The extracted string as a Lua string, and the next character to parse. +function decode_scanString(s,startPos) + base.assert(startPos, 'decode_scanString(..) called without start position') + local startChar = string.sub(s,startPos,startPos) + base.assert(startChar==[[']] or startChar==[["]],'decode_scanString called for a non-string') + local escaped = false + local endPos = startPos + 1 + local bEnded = false + local stringLen = string.len(s) + repeat + local curChar = string.sub(s,endPos,endPos) + -- Character escaping is only used to escape the string delimiters + if not escaped then + if curChar==[[\]] then + escaped = true + else + bEnded = curChar==startChar + end + else + -- If we're escaped, we accept the current character come what may + escaped = false + end + endPos = endPos + 1 + base.assert(endPos <= stringLen+1, "String decoding failed: unterminated string at position " .. endPos) + until bEnded + local stringValue = 'return ' .. string.sub(s, startPos, endPos-1) + local stringEval = base.loadstring(stringValue) + base.assert(stringEval, 'Failed to load string [ ' .. stringValue .. '] in JSON4Lua.decode_scanString at position ' .. startPos .. ' : ' .. endPos) + return stringEval(), endPos +end + +--- Scans a JSON string skipping all whitespace from the current start position. +-- Returns the position of the first non-whitespace character, or nil if the whole end of string is reached. +-- @param s The string being scanned +-- @param startPos The starting position where we should begin removing whitespace. +-- @return int The first position where non-whitespace was encountered, or string.len(s)+1 if the end of string +-- was reached. +function decode_scanWhitespace(s,startPos) + local whitespace=" \n\r\t" + local stringLen = string.len(s) + while ( string.find(whitespace, string.sub(s,startPos,startPos), 1, true) and startPos <= stringLen) do + startPos = startPos + 1 + end + return startPos +end + +--- Encodes a string to be JSON-compatible. +-- This just involves back-quoting inverted commas, back-quotes and newlines, I think ;-) +-- @param s The string to return as a JSON encoded (i.e. backquoted string) +-- @return The string appropriately escaped. +function encodeString(s) + s = string.gsub(s,'\\','\\\\') + s = string.gsub(s,'"','\\"') + s = string.gsub(s,"'","\\'") + s = string.gsub(s,'\n','\\n') + s = string.gsub(s,'\t','\\t') + return s +end + +-- Determines whether the given Lua type is an array or a table / dictionary. +-- We consider any table an array if it has indexes 1..n for its n items, and no +-- other data in the table. +-- I think this method is currently a little 'flaky', but can't think of a good way around it yet... +-- @param t The table to evaluate as an array +-- @return boolean, number True if the table can be represented as an array, false otherwise. If true, +-- the second returned value is the maximum +-- number of indexed elements in the array. +function isArray(t) + -- Next we count all the elements, ensuring that any non-indexed elements are not-encodable + -- (with the possible exception of 'n') + local maxIndex = 0 + for k,v in base.pairs(t) do + if (base.type(k)=='number' and math.floor(k)==k and 1<=k) then -- k,v is an indexed pair + if (not isEncodable(v)) then return false end -- All array elements must be encodable + maxIndex = math.max(maxIndex,k) + else + if (k=='n') then + if v ~= table.getn(t) then return false end -- False if n does not hold the number of elements + else -- Else of (k=='n') + if isEncodable(v) then return false end + end -- End of (k~='n') + end -- End of k,v not an indexed pair + end -- End of loop across all pairs + return true, maxIndex +end + +--- Determines whether the given Lua object / table / variable can be JSON encoded. The only +-- types that are JSON encodable are: string, boolean, number, nil, table and json.null. +-- In this implementation, all other types are ignored. +-- @param o The object to examine. +-- @return boolean True if the object should be JSON encoded, false if it should be ignored. +function isEncodable(o) + local t = base.type(o) + return (t=='string' or t=='boolean' or t=='number' or t=='nil' or t=='table') or (t=='function' and o==null) +end diff --git a/Mi_Lua/logging.lua b/Mi_Lua/logging.lua new file mode 100644 index 0000000..f1f7062 --- /dev/null +++ b/Mi_Lua/logging.lua @@ -0,0 +1,201 @@ +------------------------------------------------------------------------------- +-- includes a new tostring function that handles tables recursively +-- +-- @author Danilo Tuler (tuler@ideais.com.br) +-- @author Andre Carregal (info@keplerproject.org) +-- @author Thiago Costa Ponte (thiago@ideais.com.br) +-- +-- @copyright 2004-2013 Kepler Project +------------------------------------------------------------------------------- + +local type, table, string, _tostring, tonumber = type, table, string, tostring, tonumber +local select = select +local error = error +local format = string.format +local pairs = pairs +local ipairs = ipairs + +local logging = { + +-- Meta information +_COPYRIGHT = "Copyright (C) 2004-2013 Kepler Project", +_DESCRIPTION = "A simple API to use logging features in Lua", +_VERSION = "LuaLogging 1.3.0", + +-- The DEBUG Level designates fine-grained instring.formational events that are most +-- useful to debug an application +DEBUG = "DEBUG", + +-- The INFO level designates instring.formational messages that highlight the +-- progress of the application at coarse-grained level +INFO = "INFO", + +-- The WARN level designates potentially harmful situations +WARN = "WARN", + +-- The ERROR level designates error events that might still allow the +-- application to continue running +ERROR = "ERROR", + +-- The FATAL level designates very severe error events that will presumably +-- lead the application to abort +FATAL = "FATAL", +} + +local LEVEL = {"DEBUG", "INFO", "WARN", "ERROR", "FATAL"} +local MAX_LEVELS = #LEVEL +-- make level names to order +for i=1,MAX_LEVELS do + LEVEL[LEVEL[i]] = i +end + +-- private log function, with support for formating a complex log message. +local function LOG_MSG(self, level, fmt, ...) + local f_type = type(fmt) + if f_type == 'string' then + if select('#', ...) > 0 then + return self:append(level, format(fmt, ...)) + else + -- only a single string, no formating needed. + return self:append(level, fmt) + end + elseif f_type == 'function' then + -- fmt should be a callable function which returns the message to log + return self:append(level, fmt(...)) + end + -- fmt is not a string and not a function, just call tostring() on it. + return self:append(level, logging.tostring(fmt)) +end + +-- create the proxy functions for each log level. +local LEVEL_FUNCS = {} +for i=1,MAX_LEVELS do + local level = LEVEL[i] + LEVEL_FUNCS[i] = function(self, ...) + -- no level checking needed here, this function will only be called if it's level is active. + return LOG_MSG(self, level, ...) + end +end + +-- do nothing function for disabled levels. +local function disable_level() end + +-- improved assertion function. +local function assert(exp, ...) + -- if exp is true, we are finished so don't do any processing of the parameters + if exp then return exp, ... end + -- assertion failed, raise error + error(format(...), 2) +end + +------------------------------------------------------------------------------- +-- Creates a new logger object +-- @param append Function used by the logger to append a message with a +-- log-level to the log stream. +-- @return Table representing the new logger object. +------------------------------------------------------------------------------- +function logging.new(append) + if type(append) ~= "function" then + return nil, "Appender must be a function." + end + + local logger = {} + logger.append = append + + logger.setLevel = function (self, level) + local order = LEVEL[level] + assert(order, "undefined level `%s'", _tostring(level)) + if self.level then + self:log(logging.WARN, "Logger: changing loglevel from %s to %s", self.level, level) + end + self.level = level + self.level_order = order + -- enable/disable levels + for i=1,MAX_LEVELS do + local name = LEVEL[i]:lower() + if i >= order then + self[name] = LEVEL_FUNCS[i] + else + self[name] = disable_level + end + end + end + + -- generic log function. + logger.log = function (self, level, ...) + local order = LEVEL[level] + assert(order, "undefined level `%s'", _tostring(level)) + if order < self.level_order then + return + end + return LOG_MSG(self, level, ...) + end + + -- initialize log level. + logger:setLevel(logging.DEBUG) + return logger +end + + +------------------------------------------------------------------------------- +-- Prepares the log message +------------------------------------------------------------------------------- +function logging.prepareLogMsg(pattern, dt, level, message) + local logMsg = pattern or "%date %level %message\n" + message = string.gsub(message, "%%", "%%%%") + logMsg = string.gsub(logMsg, "%%date", dt) + logMsg = string.gsub(logMsg, "%%level", level) + logMsg = string.gsub(logMsg, "%%message", message) + return logMsg +end + + +------------------------------------------------------------------------------- +-- Converts a Lua value to a string +-- +-- Converts Table fields in alphabetical order +------------------------------------------------------------------------------- +local function tostring(value) + local str = '' + + if (type(value) ~= 'table') then + if (type(value) == 'string') then + str = string.format("%q", value) + else + str = _tostring(value) + end + else + local auxTable = {} + for key in pairs(value) do + if (tonumber(key) ~= key) then + table.insert(auxTable, key) + else + table.insert(auxTable, tostring(key)) + end + end + table.sort(auxTable) + + str = str..'{' + local separator = "" + local entry = "" + for _, fieldName in ipairs(auxTable) do + if ((tonumber(fieldName)) and (tonumber(fieldName) > 0)) then + entry = tostring(value[tonumber(fieldName)]) + else + entry = fieldName.." = "..tostring(value[fieldName]) + end + str = str..separator..entry + separator = ", " + end + str = str..'}' + end + return str +end +logging.tostring = tostring + +if _VERSION ~= 'Lua 5.2' then + -- still create 'logging' global for Lua versions < 5.2 + _G.logging = logging +end + +return logging diff --git a/Mi_Lua/logging/console.lua b/Mi_Lua/logging/console.lua new file mode 100644 index 0000000..433c4d8 --- /dev/null +++ b/Mi_Lua/logging/console.lua @@ -0,0 +1,20 @@ +------------------------------------------------------------------------------- +-- Prints logging information to console +-- +-- @author Thiago Costa Ponte (thiago@ideais.com.br) +-- +-- @copyright 2004-2013 Kepler Project +-- +------------------------------------------------------------------------------- + +local logging = require"logging" + +function logging.console(logPattern) + return logging.new( function(self, level, message) + io.stdout:write(logging.prepareLogMsg(logPattern, os.date(), level, message)) + return true + end) +end + +return logging.console + diff --git a/Mi_Lua/logging/email.lua b/Mi_Lua/logging/email.lua new file mode 100644 index 0000000..ae2ee8a --- /dev/null +++ b/Mi_Lua/logging/email.lua @@ -0,0 +1,43 @@ +------------------------------------------------------------------------------- +-- Emails logging information to the given recipient +-- +-- @author Thiago Costa Ponte (thiago@ideais.com.br) +-- +-- @copyright 2004-2013 Kepler Project +-- +------------------------------------------------------------------------------- + +local logging = require"logging" +local smtp = require"socket.smtp" + +function logging.email(params) + params = params or {} + params.headers = params.headers or {} + + if params.from == nil then + return nil, "'from' parameter is required" + end + if params.rcpt == nil then + return nil, "'rcpt' parameter is required" + end + + return logging.new( function(self, level, message) + local s = logging.prepareLogMsg(params.logPattern, os.date(), level, message) + if params.headers.subject then + params.headers.subject = + logging.prepareLogMsg(params.headers.subject, os.date(), level, message) + end + local msg = { headers = params.headers, body = s } + params.source = smtp.message(msg) + + local r, e = smtp.send(params) + if not r then + return nil, e + end + + return true + end) +end + +return logging.email + diff --git a/Mi_Lua/logging/file.lua b/Mi_Lua/logging/file.lua new file mode 100644 index 0000000..aade0b5 --- /dev/null +++ b/Mi_Lua/logging/file.lua @@ -0,0 +1,49 @@ +------------------------------------------------------------------------------- +-- Saves logging information in a file +-- +-- @author Thiago Costa Ponte (thiago@ideais.com.br) +-- +-- @copyright 2004-2013 Kepler Project +-- +------------------------------------------------------------------------------- + +local logging = require"logging" + +local lastFileNameDatePattern +local lastFileHandler + +local openFileLogger = function (filename, datePattern) + local filename = string.format(filename, os.date(datePattern)) + if (lastFileNameDatePattern ~= filename) then + local f = io.open(filename, "a") + if (f) then + f:setvbuf ("line") + lastFileNameDatePattern = filename + lastFileHandler = f + return f + else + return nil, string.format("file `%s' could not be opened for writing", filename) + end + else + return lastFileHandler + end +end + +function logging.file(filename, datePattern, logPattern) + if type(filename) ~= "string" then + filename = "lualogging.log" + end + + return logging.new( function(self, level, message) + local f, msg = openFileLogger(filename, datePattern) + if not f then + return nil, msg + end + local s = logging.prepareLogMsg(logPattern, os.date(), level, message) + f:write(s) + return true + end) +end + +return logging.file + diff --git a/Mi_Lua/logging/rolling_file.lua b/Mi_Lua/logging/rolling_file.lua new file mode 100644 index 0000000..3095671 --- /dev/null +++ b/Mi_Lua/logging/rolling_file.lua @@ -0,0 +1,79 @@ +--------------------------------------------------------------------------- +-- RollingFileAppender is a FileAppender that rolls over the logfile +-- once it has reached a certain size limit. It also mantains a +-- maximum number of log files. +-- +-- @author Tiago Cesar Katcipis (tiagokatcipis@gmail.com) +-- +-- @copyright 2004-2013 Kepler Project +--------------------------------------------------------------------------- + +local logging = require"logging" + +local function openFile(self) + self.file = io.open(self.filename, "a") + if not self.file then + return nil, string.format("file `%s' could not be opened for writing", self.filename) + end + self.file:setvbuf ("line") + return self.file +end + +local rollOver = function (self) + for i = self.maxIndex - 1, 1, -1 do + -- files may not exist yet, lets ignore the possible errors. + os.rename(self.filename.."."..i, self.filename.."."..i+1) + end + + self.file:close() + self.file = nil + + local _, msg = os.rename(self.filename, self.filename..".".."1") + + if msg then + return nil, string.format("error %s on log rollover", msg) + end + + return openFile(self) +end + + +local openRollingFileLogger = function (self) + if not self.file then + return openFile(self) + end + + local filesize = self.file:seek("end", 0) + + if (filesize < self.maxSize) then + return self.file + end + + return rollOver(self) +end + + +function logging.rolling_file(filename, maxFileSize, maxBackupIndex, logPattern) + if type(filename) ~= "string" then + filename = "lualogging.log" + end + + local obj = { + filename = filename, + maxSize = maxFileSize, + maxIndex = maxBackupIndex or 1 + } + + return logging.new( function(self, level, message) + local f, msg = openRollingFileLogger(obj) + if not f then + return nil, msg + end + local s = logging.prepareLogMsg(logPattern, os.date(), level, message) + f:write(s) + return true + end) +end + +return logging.rolling_file + diff --git a/Mi_Lua/logging/socket.lua b/Mi_Lua/logging/socket.lua new file mode 100644 index 0000000..74b01d7 --- /dev/null +++ b/Mi_Lua/logging/socket.lua @@ -0,0 +1,33 @@ +------------------------------------------------------------------------------- +-- Sends the logging information through a socket using luasocket +-- +-- @author Thiago Costa Ponte (thiago@ideais.com.br) +-- +-- @copyright 2004-2013 Kepler Project +-- +------------------------------------------------------------------------------- + +local logging = require"logging" +local socket = require"socket" + +function logging.socket(address, port, logPattern) + return logging.new( function(self, level, message) + local s = logging.prepareLogMsg(logPattern, os.date(), level, message) + + local socket, err = socket.connect(address, port) + if not socket then + return nil, err + end + + local cond, err = socket:send(s) + if not cond then + return nil, err + end + socket:close() + + return true + end) +end + +return logging.socket + diff --git a/Mi_Lua/logging/sql.lua b/Mi_Lua/logging/sql.lua new file mode 100644 index 0000000..e78ac9f --- /dev/null +++ b/Mi_Lua/logging/sql.lua @@ -0,0 +1,62 @@ +------------------------------------------------------------------------------- +-- Saves the logging information in a table using luasql +-- +-- @author Thiago Costa Ponte (thiago@ideais.com.br) +-- +-- @copyright 2004-2013 Kepler Project +-- +------------------------------------------------------------------------------- + +local logging = require"logging" + +function logging.sql(params) + params = params or {} + params.tablename = params.tablename or "LogTable" + params.logdatefield = params.logdatefield or "LogDate" + params.loglevelfield = params.loglevelfield or "LogLevel" + params.logmessagefield = params.logmessagefield or "LogMessage" + + if params.connectionfactory == nil or type(params.connectionfactory) ~= "function" then + return nil, "No specified connection factory function" + end + + local con, err + if params.keepalive then + con, err = params.connectionfactory() + end + + return logging.new( function(self, level, message) + if (not params.keepalive) or (con == nil) then + con, err = params.connectionfactory() + if not con then + return nil, err + end + end + + local logDate = os.date("%Y-%m-%d %H:%M:%S") + local insert = string.format("INSERT INTO %s (%s, %s, %s) VALUES ('%s', '%s', '%s')", + params.tablename, params.logdatefield, params.loglevelfield, + params.logmessagefield, logDate, level, string.gsub(message, "'", "''")) + + local ret, err = pcall(con.execute, con, insert) + if not ret then + con, err = params.connectionfactory() + if not con then + return nil, err + end + ret, err = con:execute(insert) + if not ret then + return nil, err + end + end + + if not params.keepalive then + con:close() + end + + return true + end) +end + +return logging.sql + diff --git a/Mi_Lua/lsqlite3.so b/Mi_Lua/lsqlite3.so new file mode 100644 index 0000000000000000000000000000000000000000..2482a8c512ee06dd0b061eef80285f68f41456e8 GIT binary patch literal 27456 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 literal 0 HcmV?d00001 diff --git a/Mi_Lua/ltn12.lua b/Mi_Lua/ltn12.lua new file mode 100644 index 0000000..b42689a --- /dev/null +++ b/Mi_Lua/ltn12.lua @@ -0,0 +1,292 @@ +----------------------------------------------------------------------------- +-- LTN12 - Filters, sources, sinks and pumps. +-- LuaSocket toolkit. +-- Author: Diego Nehab +-- RCS ID: $Id: ltn12.lua,v 1.31 2006/04/03 04:45:42 diego Exp $ +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Declare module +----------------------------------------------------------------------------- +local string = require("string") +local table = require("table") +local base = _G +module("ltn12") + +filter = {} +source = {} +sink = {} +pump = {} + +-- 2048 seems to be better in windows... +BLOCKSIZE = 2048 +_VERSION = "LTN12 1.0.1" + +----------------------------------------------------------------------------- +-- Filter stuff +----------------------------------------------------------------------------- +-- returns a high level filter that cycles a low-level filter +function filter.cycle(low, ctx, extra) + base.assert(low) + return function(chunk) + local ret + ret, ctx = low(ctx, chunk, extra) + return ret + end +end + +-- chains a bunch of filters together +-- (thanks to Wim Couwenberg) +function filter.chain(...) + local n = table.getn(arg) + local top, index = 1, 1 + local retry = "" + return function(chunk) + retry = chunk and retry + while true do + if index == top then + chunk = arg[index](chunk) + if chunk == "" or top == n then return chunk + elseif chunk then index = index + 1 + else + top = top+1 + index = top + end + else + chunk = arg[index](chunk or "") + if chunk == "" then + index = index - 1 + chunk = retry + elseif chunk then + if index == n then return chunk + else index = index + 1 end + else base.error("filter returned inappropriate nil") end + end + end + end +end + +----------------------------------------------------------------------------- +-- Source stuff +----------------------------------------------------------------------------- +-- create an empty source +local function empty() + return nil +end + +function source.empty() + return empty +end + +-- returns a source that just outputs an error +function source.error(err) + return function() + return nil, err + end +end + +-- creates a file source +function source.file(handle, io_err) + if handle then + return function() + local chunk = handle:read(BLOCKSIZE) + if not chunk then handle:close() end + return chunk + end + else return source.error(io_err or "unable to open file") end +end + +-- turns a fancy source into a simple source +function source.simplify(src) + base.assert(src) + return function() + local chunk, err_or_new = src() + src = err_or_new or src + if not chunk then return nil, err_or_new + else return chunk end + end +end + +-- creates string source +function source.string(s) + if s then + local i = 1 + return function() + local chunk = string.sub(s, i, i+BLOCKSIZE-1) + i = i + BLOCKSIZE + if chunk ~= "" then return chunk + else return nil end + end + else return source.empty() end +end + +-- creates rewindable source +function source.rewind(src) + base.assert(src) + local t = {} + return function(chunk) + if not chunk then + chunk = table.remove(t) + if not chunk then return src() + else return chunk end + else + table.insert(t, chunk) + end + end +end + +function source.chain(src, f) + base.assert(src and f) + local last_in, last_out = "", "" + local state = "feeding" + local err + return function() + if not last_out then + base.error('source is empty!', 2) + end + while true do + if state == "feeding" then + last_in, err = src() + if err then return nil, err end + last_out = f(last_in) + if not last_out then + if last_in then + base.error('filter returned inappropriate nil') + else + return nil + end + elseif last_out ~= "" then + state = "eating" + if last_in then last_in = "" end + return last_out + end + else + last_out = f(last_in) + if last_out == "" then + if last_in == "" then + state = "feeding" + else + base.error('filter returned ""') + end + elseif not last_out then + if last_in then + base.error('filter returned inappropriate nil') + else + return nil + end + else + return last_out + end + end + end + end +end + +-- creates a source that produces contents of several sources, one after the +-- other, as if they were concatenated +-- (thanks to Wim Couwenberg) +function source.cat(...) + local src = table.remove(arg, 1) + return function() + while src do + local chunk, err = src() + if chunk then return chunk end + if err then return nil, err end + src = table.remove(arg, 1) + end + end +end + +----------------------------------------------------------------------------- +-- Sink stuff +----------------------------------------------------------------------------- +-- creates a sink that stores into a table +function sink.table(t) + t = t or {} + local f = function(chunk, err) + if chunk then table.insert(t, chunk) end + return 1 + end + return f, t +end + +-- turns a fancy sink into a simple sink +function sink.simplify(snk) + base.assert(snk) + return function(chunk, err) + local ret, err_or_new = snk(chunk, err) + if not ret then return nil, err_or_new end + snk = err_or_new or snk + return 1 + end +end + +-- creates a file sink +function sink.file(handle, io_err) + if handle then + return function(chunk, err) + if not chunk then + handle:close() + return 1 + else return handle:write(chunk) end + end + else return sink.error(io_err or "unable to open file") end +end + +-- creates a sink that discards data +local function null() + return 1 +end + +function sink.null() + return null +end + +-- creates a sink that just returns an error +function sink.error(err) + return function() + return nil, err + end +end + +-- chains a sink with a filter +function sink.chain(f, snk) + base.assert(f and snk) + return function(chunk, err) + if chunk ~= "" then + local filtered = f(chunk) + local done = chunk and "" + while true do + local ret, snkerr = snk(filtered, err) + if not ret then return nil, snkerr end + if filtered == done then return 1 end + filtered = f(done) + end + else return 1 end + end +end + +----------------------------------------------------------------------------- +-- Pump stuff +----------------------------------------------------------------------------- +-- pumps one chunk from the source to the sink +function pump.step(src, snk) + local chunk, src_err = src() + local ret, snk_err = snk(chunk, src_err) + if chunk and ret then return 1 + else return nil, src_err or snk_err end +end + +-- pumps all data from a source to a sink, using a step function +function pump.all(src, snk, step) + base.assert(src and snk) + step = step or pump.step + while true do + local ret, err = step(src, snk) + if not ret then + if err then return nil, err + else return 1 end + end + end +end + diff --git a/Mi_Lua/luci/cacheloader.lua b/Mi_Lua/luci/cacheloader.lua new file mode 100644 index 0000000..942c4b7 --- /dev/null +++ b/Mi_Lua/luci/cacheloader.lua @@ -0,0 +1,23 @@ +--[[ +LuCI - Lua Configuration Interface + +Copyright 2008 Steven Barth +Copyright 2008 Jo-Philipp Wich + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +$Id$ +]]-- + +local config = require "luci.config" +local ccache = require "luci.ccache" + +module "luci.cacheloader" + +if config.ccache and config.ccache.enable == "1" then + ccache.cache_ondemand() +end \ No newline at end of file diff --git a/Mi_Lua/luci/cbi.lua b/Mi_Lua/luci/cbi.lua new file mode 100644 index 0000000..c84d3a0 --- /dev/null +++ b/Mi_Lua/luci/cbi.lua @@ -0,0 +1,1850 @@ +--[[ +LuCI - Configuration Bind Interface + +Description: +Offers an interface for binding configuration values to certain +data types. Supports value and range validation and basic dependencies. + +FileId: +$Id: cbi.lua 9558 2012-12-18 13:58:22Z jow $ + +License: +Copyright 2008 Steven Barth + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +]]-- +module("luci.cbi", package.seeall) + +require("luci.template") +local util = require("luci.util") +require("luci.http") + + +--local event = require "luci.sys.event" +local fs = require("nixio.fs") +local uci = require("luci.model.uci") +local datatypes = require("luci.cbi.datatypes") +local class = util.class +local instanceof = util.instanceof + +FORM_NODATA = 0 +FORM_PROCEED = 0 +FORM_VALID = 1 +FORM_DONE = 1 +FORM_INVALID = -1 +FORM_CHANGED = 2 +FORM_SKIP = 4 + +AUTO = true + +CREATE_PREFIX = "cbi.cts." +REMOVE_PREFIX = "cbi.rts." +RESORT_PREFIX = "cbi.sts." +FEXIST_PREFIX = "cbi.cbe." + +-- Loads a CBI map from given file, creating an environment and returns it +function load(cbimap, ...) + local fs = require "nixio.fs" + local i18n = require "luci.i18n" + require("luci.config") + require("luci.util") + + local upldir = "/lib/uci/upload/" + local cbidir = luci.util.libpath() .. "/model/cbi/" + local func, err + + if fs.access(cbidir..cbimap..".lua") then + func, err = loadfile(cbidir..cbimap..".lua") + elseif fs.access(cbimap) then + func, err = loadfile(cbimap) + else + func, err = nil, "Model '" .. cbimap .. "' not found!" + end + + assert(func, err) + + local env = { + translate=i18n.translate, + translatef=i18n.translatef, + arg={...} + } + + setfenv(func, setmetatable(env, {__index = + function(tbl, key) + return rawget(tbl, key) or _M[key] or _G[key] + end})) + + local maps = { func() } + local uploads = { } + local has_upload = false + + for i, map in ipairs(maps) do + if not instanceof(map, Node) then + error("CBI map returns no valid map object!") + return nil + else + map:prepare() + if map.upload_fields then + has_upload = true + for _, field in ipairs(map.upload_fields) do + uploads[ + field.config .. '.' .. + (field.section.sectiontype or '1') .. '.' .. + field.option + ] = true + end + end + end + end + + if has_upload then + local uci = luci.model.uci.cursor() + local prm = luci.http.context.request.message.params + local fd, cbid + + luci.http.setfilehandler( + function( field, chunk, eof ) + if not field then return end + if field.name and not cbid then + local c, s, o = field.name:gmatch( + "cbid%.([^%.]+)%.([^%.]+)%.([^%.]+)" + )() + + if c and s and o then + local t = uci:get( c, s ) or s + if uploads[c.."."..t.."."..o] then + local path = upldir .. field.name + fd = io.open(path, "w") + if fd then + cbid = field.name + prm[cbid] = path + end + end + end + end + + if field.name == cbid and fd then + fd:write(chunk) + end + + if eof and fd then + fd:close() + fd = nil + cbid = nil + end + end + ) + end + + return maps +end + +-- +-- Compile a datatype specification into a parse tree for evaluation later on +-- +local cdt_cache = { } + +function compile_datatype(code) + local i + local pos = 0 + local esc = false + local depth = 0 + local stack = { } + + for i = 1, #code+1 do + local byte = code:byte(i) or 44 + if esc then + esc = false + elseif byte == 92 then + esc = true + elseif byte == 40 or byte == 44 then + if depth <= 0 then + if pos < i then + local label = code:sub(pos, i-1) + :gsub("\\(.)", "%1") + :gsub("^%s+", "") + :gsub("%s+$", "") + + if #label > 0 and tonumber(label) then + stack[#stack+1] = tonumber(label) + elseif label:match("^'.*'$") or label:match('^".*"$') then + stack[#stack+1] = label:gsub("[\"'](.*)[\"']", "%1") + elseif type(datatypes[label]) == "function" then + stack[#stack+1] = datatypes[label] + stack[#stack+1] = { } + else + error("Datatype error, bad token %q" % label) + end + end + pos = i + 1 + end + depth = depth + (byte == 40 and 1 or 0) + elseif byte == 41 then + depth = depth - 1 + if depth <= 0 then + if type(stack[#stack-1]) ~= "function" then + error("Datatype error, argument list follows non-function") + end + stack[#stack] = compile_datatype(code:sub(pos, i-1)) + pos = i + 1 + end + end + end + + return stack +end + +function verify_datatype(dt, value) + if dt and #dt > 0 then + if not cdt_cache[dt] then + local c = compile_datatype(dt) + if c and type(c[1]) == "function" then + cdt_cache[dt] = c + else + error("Datatype error, not a function expression") + end + end + if cdt_cache[dt] then + return cdt_cache[dt][1](value, unpack(cdt_cache[dt][2])) + end + end + return true +end + + +-- Node pseudo abstract class +Node = class() + +function Node.__init__(self, title, description) + self.children = {} + self.title = title or "" + self.description = description or "" + self.template = "cbi/node" +end + +-- hook helper +function Node._run_hook(self, hook) + if type(self[hook]) == "function" then + return self[hook](self) + end +end + +function Node._run_hooks(self, ...) + local f + local r = false + for _, f in ipairs(arg) do + if type(self[f]) == "function" then + self[f](self) + r = true + end + end + return r +end + +-- Prepare nodes +function Node.prepare(self, ...) + for k, child in ipairs(self.children) do + child:prepare(...) + end +end + +-- Append child nodes +function Node.append(self, obj) + table.insert(self.children, obj) +end + +-- Parse this node and its children +function Node.parse(self, ...) + for k, child in ipairs(self.children) do + child:parse(...) + end +end + +-- Render this node +function Node.render(self, scope) + scope = scope or {} + scope.self = self + + luci.template.render(self.template, scope) +end + +-- Render the children +function Node.render_children(self, ...) + local k, node + for k, node in ipairs(self.children) do + node.last_child = (k == #self.children) + node:render(...) + end +end + + +--[[ +A simple template element +]]-- +Template = class(Node) + +function Template.__init__(self, template) + Node.__init__(self) + self.template = template +end + +function Template.render(self) + luci.template.render(self.template, {self=self}) +end + +function Template.parse(self, readinput) + self.readinput = (readinput ~= false) + return Map.formvalue(self, "cbi.submit") and FORM_DONE or FORM_NODATA +end + + +--[[ +Map - A map describing a configuration file +]]-- +Map = class(Node) + +function Map.__init__(self, config, ...) + Node.__init__(self, ...) + + self.config = config + self.parsechain = {self.config} + self.template = "cbi/map" + self.apply_on_parse = nil + self.readinput = true + self.proceed = false + self.flow = {} + + self.uci = uci.cursor() + self.save = true + + self.changed = false + + if not self.uci:load(self.config) then + error("Unable to read UCI data: " .. self.config) + end +end + +function Map.formvalue(self, key) + return self.readinput and luci.http.formvalue(key) +end + +function Map.formvaluetable(self, key) + return self.readinput and luci.http.formvaluetable(key) or {} +end + +function Map.get_scheme(self, sectiontype, option) + if not option then + return self.scheme and self.scheme.sections[sectiontype] + else + return self.scheme and self.scheme.variables[sectiontype] + and self.scheme.variables[sectiontype][option] + end +end + +function Map.submitstate(self) + return self:formvalue("cbi.submit") +end + +-- Chain foreign config +function Map.chain(self, config) + table.insert(self.parsechain, config) +end + +function Map.state_handler(self, state) + return state +end + +-- Use optimized UCI writing +function Map.parse(self, readinput, ...) + self.readinput = (readinput ~= false) + self:_run_hooks("on_parse") + + if self:formvalue("cbi.skip") then + self.state = FORM_SKIP + return self:state_handler(self.state) + end + + Node.parse(self, ...) + + if self.save then + self:_run_hooks("on_save", "on_before_save") + for i, config in ipairs(self.parsechain) do + self.uci:save(config) + end + self:_run_hooks("on_after_save") + if self:submitstate() and ((not self.proceed and self.flow.autoapply) or luci.http.formvalue("cbi.apply")) then + self:_run_hooks("on_before_commit") + for i, config in ipairs(self.parsechain) do + self.uci:commit(config) + + -- Refresh data because commit changes section names + self.uci:load(config) + end + self:_run_hooks("on_commit", "on_after_commit", "on_before_apply") + if self.apply_on_parse then + self.uci:apply(self.parsechain) + self:_run_hooks("on_apply", "on_after_apply") + else + -- This is evaluated by the dispatcher and delegated to the + -- template which in turn fires XHR to perform the actual + -- apply actions. + self.apply_needed = true + end + + -- Reparse sections + Node.parse(self, true) + + end + for i, config in ipairs(self.parsechain) do + self.uci:unload(config) + end + if type(self.commit_handler) == "function" then + self:commit_handler(self:submitstate()) + end + end + + if self:submitstate() then + if not self.save then + self.state = FORM_INVALID + elseif self.proceed then + self.state = FORM_PROCEED + else + self.state = self.changed and FORM_CHANGED or FORM_VALID + end + else + self.state = FORM_NODATA + end + + return self:state_handler(self.state) +end + +function Map.render(self, ...) + self:_run_hooks("on_init") + Node.render(self, ...) +end + +-- Creates a child section +function Map.section(self, class, ...) + if instanceof(class, AbstractSection) then + local obj = class(self, ...) + self:append(obj) + return obj + else + error("class must be a descendent of AbstractSection") + end +end + +-- UCI add +function Map.add(self, sectiontype) + return self.uci:add(self.config, sectiontype) +end + +-- UCI set +function Map.set(self, section, option, value) + if type(value) ~= "table" or #value > 0 then + if option then + return self.uci:set(self.config, section, option, value) + else + return self.uci:set(self.config, section, value) + end + else + return Map.del(self, section, option) + end +end + +-- UCI del +function Map.del(self, section, option) + if option then + return self.uci:delete(self.config, section, option) + else + return self.uci:delete(self.config, section) + end +end + +-- UCI get +function Map.get(self, section, option) + if not section then + return self.uci:get_all(self.config) + elseif option then + return self.uci:get(self.config, section, option) + else + return self.uci:get_all(self.config, section) + end +end + +--[[ +Compound - Container +]]-- +Compound = class(Node) + +function Compound.__init__(self, ...) + Node.__init__(self) + self.template = "cbi/compound" + self.children = {...} +end + +function Compound.populate_delegator(self, delegator) + for _, v in ipairs(self.children) do + v.delegator = delegator + end +end + +function Compound.parse(self, ...) + local cstate, state = 0 + + for k, child in ipairs(self.children) do + cstate = child:parse(...) + state = (not state or cstate < state) and cstate or state + end + + return state +end + + +--[[ +Delegator - Node controller +]]-- +Delegator = class(Node) +function Delegator.__init__(self, ...) + Node.__init__(self, ...) + self.nodes = {} + self.defaultpath = {} + self.pageaction = false + self.readinput = true + self.allow_reset = false + self.allow_cancel = false + self.allow_back = false + self.allow_finish = false + self.template = "cbi/delegator" +end + +function Delegator.set(self, name, node) + assert(not self.nodes[name], "Duplicate entry") + + self.nodes[name] = node +end + +function Delegator.add(self, name, node) + node = self:set(name, node) + self.defaultpath[#self.defaultpath+1] = name +end + +function Delegator.insert_after(self, name, after) + local n = #self.chain + 1 + for k, v in ipairs(self.chain) do + if v == after then + n = k + 1 + break + end + end + table.insert(self.chain, n, name) +end + +function Delegator.set_route(self, ...) + local n, chain, route = 0, self.chain, {...} + for i = 1, #chain do + if chain[i] == self.current then + n = i + break + end + end + for i = 1, #route do + n = n + 1 + chain[n] = route[i] + end + for i = n + 1, #chain do + chain[i] = nil + end +end + +function Delegator.get(self, name) + local node = self.nodes[name] + + if type(node) == "string" then + node = load(node, name) + end + + if type(node) == "table" and getmetatable(node) == nil then + node = Compound(unpack(node)) + end + + return node +end + +function Delegator.parse(self, ...) + if self.allow_cancel and Map.formvalue(self, "cbi.cancel") then + if self:_run_hooks("on_cancel") then + return FORM_DONE + end + end + + if not Map.formvalue(self, "cbi.delg.current") then + self:_run_hooks("on_init") + end + + local newcurrent + self.chain = self.chain or self:get_chain() + self.current = self.current or self:get_active() + self.active = self.active or self:get(self.current) + assert(self.active, "Invalid state") + + local stat = FORM_DONE + if type(self.active) ~= "function" then + self.active:populate_delegator(self) + stat = self.active:parse() + else + self:active() + end + + if stat > FORM_PROCEED then + if Map.formvalue(self, "cbi.delg.back") then + newcurrent = self:get_prev(self.current) + else + newcurrent = self:get_next(self.current) + end + elseif stat < FORM_PROCEED then + return stat + end + + + if not Map.formvalue(self, "cbi.submit") then + return FORM_NODATA + elseif stat > FORM_PROCEED + and (not newcurrent or not self:get(newcurrent)) then + return self:_run_hook("on_done") or FORM_DONE + else + self.current = newcurrent or self.current + self.active = self:get(self.current) + if type(self.active) ~= "function" then + self.active:populate_delegator(self) + local stat = self.active:parse(false) + if stat == FORM_SKIP then + return self:parse(...) + else + return FORM_PROCEED + end + else + return self:parse(...) + end + end +end + +function Delegator.get_next(self, state) + for k, v in ipairs(self.chain) do + if v == state then + return self.chain[k+1] + end + end +end + +function Delegator.get_prev(self, state) + for k, v in ipairs(self.chain) do + if v == state then + return self.chain[k-1] + end + end +end + +function Delegator.get_chain(self) + local x = Map.formvalue(self, "cbi.delg.path") or self.defaultpath + return type(x) == "table" and x or {x} +end + +function Delegator.get_active(self) + return Map.formvalue(self, "cbi.delg.current") or self.chain[1] +end + +--[[ +Page - A simple node +]]-- + +Page = class(Node) +Page.__init__ = Node.__init__ +Page.parse = function() end + + +--[[ +SimpleForm - A Simple non-UCI form +]]-- +SimpleForm = class(Node) + +function SimpleForm.__init__(self, config, title, description, data) + Node.__init__(self, title, description) + self.config = config + self.data = data or {} + self.template = "cbi/simpleform" + self.dorender = true + self.pageaction = false + self.readinput = true +end + +SimpleForm.formvalue = Map.formvalue +SimpleForm.formvaluetable = Map.formvaluetable + +function SimpleForm.parse(self, readinput, ...) + self.readinput = (readinput ~= false) + + if self:formvalue("cbi.skip") then + return FORM_SKIP + end + + if self:formvalue("cbi.cancel") and self:_run_hooks("on_cancel") then + return FORM_DONE + end + + if self:submitstate() then + Node.parse(self, 1, ...) + end + + local valid = true + for k, j in ipairs(self.children) do + for i, v in ipairs(j.children) do + valid = valid + and (not v.tag_missing or not v.tag_missing[1]) + and (not v.tag_invalid or not v.tag_invalid[1]) + and (not v.error) + end + end + + local state = + not self:submitstate() and FORM_NODATA + or valid and FORM_VALID + or FORM_INVALID + + self.dorender = not self.handle + if self.handle then + local nrender, nstate = self:handle(state, self.data) + self.dorender = self.dorender or (nrender ~= false) + state = nstate or state + end + return state +end + +function SimpleForm.render(self, ...) + if self.dorender then + Node.render(self, ...) + end +end + +function SimpleForm.submitstate(self) + return self:formvalue("cbi.submit") +end + +function SimpleForm.section(self, class, ...) + if instanceof(class, AbstractSection) then + local obj = class(self, ...) + self:append(obj) + return obj + else + error("class must be a descendent of AbstractSection") + end +end + +-- Creates a child field +function SimpleForm.field(self, class, ...) + local section + for k, v in ipairs(self.children) do + if instanceof(v, SimpleSection) then + section = v + break + end + end + if not section then + section = self:section(SimpleSection) + end + + if instanceof(class, AbstractValue) then + local obj = class(self, section, ...) + obj.track_missing = true + section:append(obj) + return obj + else + error("class must be a descendent of AbstractValue") + end +end + +function SimpleForm.set(self, section, option, value) + self.data[option] = value +end + + +function SimpleForm.del(self, section, option) + self.data[option] = nil +end + + +function SimpleForm.get(self, section, option) + return self.data[option] +end + + +function SimpleForm.get_scheme() + return nil +end + + +Form = class(SimpleForm) + +function Form.__init__(self, ...) + SimpleForm.__init__(self, ...) + self.embedded = true +end + + +--[[ +AbstractSection +]]-- +AbstractSection = class(Node) + +function AbstractSection.__init__(self, map, sectiontype, ...) + Node.__init__(self, ...) + self.sectiontype = sectiontype + self.map = map + self.config = map.config + self.optionals = {} + self.defaults = {} + self.fields = {} + self.tag_error = {} + self.tag_invalid = {} + self.tag_deperror = {} + self.changed = false + + self.optional = true + self.addremove = false + self.dynamic = false +end + +-- Define a tab for the section +function AbstractSection.tab(self, tab, title, desc) + self.tabs = self.tabs or { } + self.tab_names = self.tab_names or { } + + self.tab_names[#self.tab_names+1] = tab + self.tabs[tab] = { + title = title, + description = desc, + childs = { } + } +end + +-- Check whether the section has tabs +function AbstractSection.has_tabs(self) + return (self.tabs ~= nil) and (next(self.tabs) ~= nil) +end + +-- Appends a new option +function AbstractSection.option(self, class, option, ...) + if instanceof(class, AbstractValue) then + local obj = class(self.map, self, option, ...) + self:append(obj) + self.fields[option] = obj + return obj + elseif class == true then + error("No valid class was given and autodetection failed.") + else + error("class must be a descendant of AbstractValue") + end +end + +-- Appends a new tabbed option +function AbstractSection.taboption(self, tab, ...) + + assert(tab and self.tabs and self.tabs[tab], + "Cannot assign option to not existing tab %q" % tostring(tab)) + + local l = self.tabs[tab].childs + local o = AbstractSection.option(self, ...) + + if o then l[#l+1] = o end + + return o +end + +-- Render a single tab +function AbstractSection.render_tab(self, tab, ...) + + assert(tab and self.tabs and self.tabs[tab], + "Cannot render not existing tab %q" % tostring(tab)) + + local k, node + for k, node in ipairs(self.tabs[tab].childs) do + node.last_child = (k == #self.tabs[tab].childs) + node:render(...) + end +end + +-- Parse optional options +function AbstractSection.parse_optionals(self, section) + if not self.optional then + return + end + + self.optionals[section] = {} + + local field = self.map:formvalue("cbi.opt."..self.config.."."..section) + for k,v in ipairs(self.children) do + if v.optional and not v:cfgvalue(section) and not self:has_tabs() then + if field == v.option then + field = nil + self.map.proceed = true + else + table.insert(self.optionals[section], v) + end + end + end + + if field and #field > 0 and self.dynamic then + self:add_dynamic(field) + end +end + +-- Add a dynamic option +function AbstractSection.add_dynamic(self, field, optional) + local o = self:option(Value, field, field) + o.optional = optional +end + +-- Parse all dynamic options +function AbstractSection.parse_dynamic(self, section) + if not self.dynamic then + return + end + + local arr = luci.util.clone(self:cfgvalue(section)) + local form = self.map:formvaluetable("cbid."..self.config.."."..section) + for k, v in pairs(form) do + arr[k] = v + end + + for key,val in pairs(arr) do + local create = true + + for i,c in ipairs(self.children) do + if c.option == key then + create = false + end + end + + if create and key:sub(1, 1) ~= "." then + self.map.proceed = true + self:add_dynamic(key, true) + end + end +end + +-- Returns the section's UCI table +function AbstractSection.cfgvalue(self, section) + return self.map:get(section) +end + +-- Push events +function AbstractSection.push_events(self) + --luci.util.append(self.map.events, self.events) + self.map.changed = true +end + +-- Removes the section +function AbstractSection.remove(self, section) + self.map.proceed = true + return self.map:del(section) +end + +-- Creates the section +function AbstractSection.create(self, section) + local stat + + if section then + stat = section:match("^[%w_]+$") and self.map:set(section, nil, self.sectiontype) + else + section = self.map:add(self.sectiontype) + stat = section + end + + if stat then + for k,v in pairs(self.children) do + if v.default then + self.map:set(section, v.option, v.default) + end + end + + for k,v in pairs(self.defaults) do + self.map:set(section, k, v) + end + end + + self.map.proceed = true + + return stat +end + + +SimpleSection = class(AbstractSection) + +function SimpleSection.__init__(self, form, ...) + AbstractSection.__init__(self, form, nil, ...) + self.template = "cbi/nullsection" +end + + +Table = class(AbstractSection) + +function Table.__init__(self, form, data, ...) + local datasource = {} + local tself = self + datasource.config = "table" + self.data = data or {} + + datasource.formvalue = Map.formvalue + datasource.formvaluetable = Map.formvaluetable + datasource.readinput = true + + function datasource.get(self, section, option) + return tself.data[section] and tself.data[section][option] + end + + function datasource.submitstate(self) + return Map.formvalue(self, "cbi.submit") + end + + function datasource.del(...) + return true + end + + function datasource.get_scheme() + return nil + end + + AbstractSection.__init__(self, datasource, "table", ...) + self.template = "cbi/tblsection" + self.rowcolors = true + self.anonymous = true +end + +function Table.parse(self, readinput) + self.map.readinput = (readinput ~= false) + for i, k in ipairs(self:cfgsections()) do + if self.map:submitstate() then + Node.parse(self, k) + end + end +end + +function Table.cfgsections(self) + local sections = {} + + for i, v in luci.util.kspairs(self.data) do + table.insert(sections, i) + end + + return sections +end + +function Table.update(self, data) + self.data = data +end + + + +--[[ +NamedSection - A fixed configuration section defined by its name +]]-- +NamedSection = class(AbstractSection) + +function NamedSection.__init__(self, map, section, stype, ...) + AbstractSection.__init__(self, map, stype, ...) + + -- Defaults + self.addremove = false + self.template = "cbi/nsection" + self.section = section +end + +function NamedSection.parse(self, novld) + local s = self.section + local active = self:cfgvalue(s) + + if self.addremove then + local path = self.config.."."..s + if active then -- Remove the section + if self.map:formvalue("cbi.rns."..path) and self:remove(s) then + self:push_events() + return + end + else -- Create and apply default values + if self.map:formvalue("cbi.cns."..path) then + self:create(s) + return + end + end + end + + if active then + AbstractSection.parse_dynamic(self, s) + if self.map:submitstate() then + Node.parse(self, s) + end + AbstractSection.parse_optionals(self, s) + + if self.changed then + self:push_events() + end + end +end + + +--[[ +TypedSection - A (set of) configuration section(s) defined by the type + addremove: Defines whether the user can add/remove sections of this type + anonymous: Allow creating anonymous sections + validate: a validation function returning nil if the section is invalid +]]-- +TypedSection = class(AbstractSection) + +function TypedSection.__init__(self, map, type, ...) + AbstractSection.__init__(self, map, type, ...) + + self.template = "cbi/tsection" + self.deps = {} + self.anonymous = false +end + +-- Return all matching UCI sections for this TypedSection +function TypedSection.cfgsections(self) + local sections = {} + self.map.uci:foreach(self.map.config, self.sectiontype, + function (section) + if self:checkscope(section[".name"]) then + table.insert(sections, section[".name"]) + end + end) + + return sections +end + +-- Limits scope to sections that have certain option => value pairs +function TypedSection.depends(self, option, value) + table.insert(self.deps, {option=option, value=value}) +end + +function TypedSection.parse(self, novld) + if self.addremove then + -- Remove + local crval = REMOVE_PREFIX .. self.config + local name = self.map:formvaluetable(crval) + for k,v in pairs(name) do + if k:sub(-2) == ".x" then + k = k:sub(1, #k - 2) + end + if self:cfgvalue(k) and self:checkscope(k) then + self:remove(k) + end + end + end + + local co + for i, k in ipairs(self:cfgsections()) do + AbstractSection.parse_dynamic(self, k) + if self.map:submitstate() then + Node.parse(self, k, novld) + end + AbstractSection.parse_optionals(self, k) + end + + if self.addremove then + -- Create + local created + local crval = CREATE_PREFIX .. self.config .. "." .. self.sectiontype + local origin, name = next(self.map:formvaluetable(crval)) + if self.anonymous then + if name then + created = self:create(nil, origin) + end + else + if name then + -- Ignore if it already exists + if self:cfgvalue(name) then + name = nil; + end + + name = self:checkscope(name) + + if not name then + self.err_invalid = true + end + + if name and #name > 0 then + created = self:create(name, origin) and name + if not created then + self.invalid_cts = true + end + end + end + end + + if created then + AbstractSection.parse_optionals(self, created) + end + end + + if self.sortable then + local stval = RESORT_PREFIX .. self.config .. "." .. self.sectiontype + local order = self.map:formvalue(stval) + if order and #order > 0 then + local sid + local num = 0 + for sid in util.imatch(order) do + self.map.uci:reorder(self.config, sid, num) + num = num + 1 + end + self.changed = (num > 0) + end + end + + if created or self.changed then + self:push_events() + end +end + +-- Verifies scope of sections +function TypedSection.checkscope(self, section) + -- Check if we are not excluded + if self.filter and not self:filter(section) then + return nil + end + + -- Check if at least one dependency is met + if #self.deps > 0 and self:cfgvalue(section) then + local stat = false + + for k, v in ipairs(self.deps) do + if self:cfgvalue(section)[v.option] == v.value then + stat = true + end + end + + if not stat then + return nil + end + end + + return self:validate(section) +end + + +-- Dummy validate function +function TypedSection.validate(self, section) + return section +end + + +--[[ +AbstractValue - An abstract Value Type + null: Value can be empty + valid: A function returning the value if it is valid otherwise nil + depends: A table of option => value pairs of which one must be true + default: The default value + size: The size of the input fields + rmempty: Unset value if empty + optional: This value is optional (see AbstractSection.optionals) +]]-- +AbstractValue = class(Node) + +function AbstractValue.__init__(self, map, section, option, ...) + Node.__init__(self, ...) + self.section = section + self.option = option + self.map = map + self.config = map.config + self.tag_invalid = {} + self.tag_missing = {} + self.tag_reqerror = {} + self.tag_error = {} + self.deps = {} + self.subdeps = {} + --self.cast = "string" + + self.track_missing = false + self.rmempty = true + self.default = nil + self.size = nil + self.optional = false +end + +function AbstractValue.prepare(self) + self.cast = self.cast or "string" +end + +-- Add a dependencie to another section field +function AbstractValue.depends(self, field, value) + local deps + if type(field) == "string" then + deps = {} + deps[field] = value + else + deps = field + end + + table.insert(self.deps, {deps=deps, add=""}) +end + +-- Generates the unique CBID +function AbstractValue.cbid(self, section) + return "cbid."..self.map.config.."."..section.."."..self.option +end + +-- Return whether this object should be created +function AbstractValue.formcreated(self, section) + local key = "cbi.opt."..self.config.."."..section + return (self.map:formvalue(key) == self.option) +end + +-- Returns the formvalue for this object +function AbstractValue.formvalue(self, section) + return self.map:formvalue(self:cbid(section)) +end + +function AbstractValue.additional(self, value) + self.optional = value +end + +function AbstractValue.mandatory(self, value) + self.rmempty = not value +end + +function AbstractValue.add_error(self, section, type, msg) + self.error = self.error or { } + self.error[section] = msg or type + + self.section.error = self.section.error or { } + self.section.error[section] = self.section.error[section] or { } + table.insert(self.section.error[section], msg or type) + + if type == "invalid" then + self.tag_invalid[section] = true + elseif type == "missing" then + self.tag_missing[section] = true + end + + self.tag_error[section] = true + self.map.save = false +end + +function AbstractValue.parse(self, section, novld) + local fvalue = self:formvalue(section) + local cvalue = self:cfgvalue(section) + + -- If favlue and cvalue are both tables and have the same content + -- make them identical + if type(fvalue) == "table" and type(cvalue) == "table" then + local equal = #fvalue == #cvalue + if equal then + for i=1, #fvalue do + if cvalue[i] ~= fvalue[i] then + equal = false + end + end + end + if equal then + fvalue = cvalue + end + end + + if fvalue and #fvalue > 0 then -- If we have a form value, write it to UCI + local val_err + fvalue, val_err = self:validate(fvalue, section) + fvalue = self:transform(fvalue) + + if not fvalue and not novld then + self:add_error(section, "invalid", val_err) + end + + if fvalue and (self.forcewrite or not (fvalue == cvalue)) then + if self:write(section, fvalue) then + -- Push events + self.section.changed = true + --luci.util.append(self.map.events, self.events) + end + end + else -- Unset the UCI or error + if self.rmempty or self.optional then + if self:remove(section) then + -- Push events + self.section.changed = true + --luci.util.append(self.map.events, self.events) + end + elseif cvalue ~= fvalue and not novld then + -- trigger validator with nil value to get custom user error msg. + local _, val_err = self:validate(nil, section) + self:add_error(section, "missing", val_err) + end + end +end + +-- Render if this value exists or if it is mandatory +function AbstractValue.render(self, s, scope) + if not self.optional or self.section:has_tabs() or self:cfgvalue(s) or self:formcreated(s) then + scope = scope or {} + scope.section = s + scope.cbid = self:cbid(s) + Node.render(self, scope) + end +end + +-- Return the UCI value of this object +function AbstractValue.cfgvalue(self, section) + local value + if self.tag_error[section] then + value = self:formvalue(section) + else + value = self.map:get(section, self.option) + end + + if not value then + return nil + elseif not self.cast or self.cast == type(value) then + return value + elseif self.cast == "string" then + if type(value) == "table" then + return value[1] + end + elseif self.cast == "table" then + return { value } + end +end + +-- Validate the form value +function AbstractValue.validate(self, value) + if self.datatype and value then + if type(value) == "table" then + local v + for _, v in ipairs(value) do + if v and #v > 0 and not verify_datatype(self.datatype, v) then + return nil + end + end + else + if not verify_datatype(self.datatype, value) then + return nil + end + end + end + + return value +end + +AbstractValue.transform = AbstractValue.validate + + +-- Write to UCI +function AbstractValue.write(self, section, value) + return self.map:set(section, self.option, value) +end + +-- Remove from UCI +function AbstractValue.remove(self, section) + return self.map:del(section, self.option) +end + + + + +--[[ +Value - A one-line value + maxlength: The maximum length +]]-- +Value = class(AbstractValue) + +function Value.__init__(self, ...) + AbstractValue.__init__(self, ...) + self.template = "cbi/value" + self.keylist = {} + self.vallist = {} +end + +function Value.reset_values(self) + self.keylist = {} + self.vallist = {} +end + +function Value.value(self, key, val) + val = val or key + table.insert(self.keylist, tostring(key)) + table.insert(self.vallist, tostring(val)) +end + + +-- DummyValue - This does nothing except being there +DummyValue = class(AbstractValue) + +function DummyValue.__init__(self, ...) + AbstractValue.__init__(self, ...) + self.template = "cbi/dvalue" + self.value = nil +end + +function DummyValue.cfgvalue(self, section) + local value + if self.value then + if type(self.value) == "function" then + value = self:value(section) + else + value = self.value + end + else + value = AbstractValue.cfgvalue(self, section) + end + return value +end + +function DummyValue.parse(self) + +end + + +--[[ +Flag - A flag being enabled or disabled +]]-- +Flag = class(AbstractValue) + +function Flag.__init__(self, ...) + AbstractValue.__init__(self, ...) + self.template = "cbi/fvalue" + + self.enabled = "1" + self.disabled = "0" + self.default = self.disabled +end + +-- A flag can only have two states: set or unset +function Flag.parse(self, section) + local fexists = self.map:formvalue( + FEXIST_PREFIX .. self.config .. "." .. section .. "." .. self.option) + + if fexists then + local fvalue = self:formvalue(section) and self.enabled or self.disabled + if fvalue ~= self.default or (not self.optional and not self.rmempty) then + self:write(section, fvalue) + else + self:remove(section) + end + else + self:remove(section) + end +end + +function Flag.cfgvalue(self, section) + return AbstractValue.cfgvalue(self, section) or self.default +end + + +--[[ +ListValue - A one-line value predefined in a list + widget: The widget that will be used (select, radio) +]]-- +ListValue = class(AbstractValue) + +function ListValue.__init__(self, ...) + AbstractValue.__init__(self, ...) + self.template = "cbi/lvalue" + + self.keylist = {} + self.vallist = {} + self.size = 1 + self.widget = "select" +end + +function ListValue.reset_values(self) + self.keylist = {} + self.vallist = {} +end + +function ListValue.value(self, key, val, ...) + if luci.util.contains(self.keylist, key) then + return + end + + val = val or key + table.insert(self.keylist, tostring(key)) + table.insert(self.vallist, tostring(val)) + + for i, deps in ipairs({...}) do + self.subdeps[#self.subdeps + 1] = {add = "-"..key, deps=deps} + end +end + +function ListValue.validate(self, val) + if luci.util.contains(self.keylist, val) then + return val + else + return nil + end +end + + + +--[[ +MultiValue - Multiple delimited values + widget: The widget that will be used (select, checkbox) + delimiter: The delimiter that will separate the values (default: " ") +]]-- +MultiValue = class(AbstractValue) + +function MultiValue.__init__(self, ...) + AbstractValue.__init__(self, ...) + self.template = "cbi/mvalue" + + self.keylist = {} + self.vallist = {} + + self.widget = "checkbox" + self.delimiter = " " +end + +function MultiValue.render(self, ...) + if self.widget == "select" and not self.size then + self.size = #self.vallist + end + + AbstractValue.render(self, ...) +end + +function MultiValue.reset_values(self) + self.keylist = {} + self.vallist = {} +end + +function MultiValue.value(self, key, val) + if luci.util.contains(self.keylist, key) then + return + end + + val = val or key + table.insert(self.keylist, tostring(key)) + table.insert(self.vallist, tostring(val)) +end + +function MultiValue.valuelist(self, section) + local val = self:cfgvalue(section) + + if not(type(val) == "string") then + return {} + end + + return luci.util.split(val, self.delimiter) +end + +function MultiValue.validate(self, val) + val = (type(val) == "table") and val or {val} + + local result + + for i, value in ipairs(val) do + if luci.util.contains(self.keylist, value) then + result = result and (result .. self.delimiter .. value) or value + end + end + + return result +end + + +StaticList = class(MultiValue) + +function StaticList.__init__(self, ...) + MultiValue.__init__(self, ...) + self.cast = "table" + self.valuelist = self.cfgvalue + + if not self.override_scheme + and self.map:get_scheme(self.section.sectiontype, self.option) then + local vs = self.map:get_scheme(self.section.sectiontype, self.option) + if self.value and vs.values and not self.override_values then + for k, v in pairs(vs.values) do + self:value(k, v) + end + end + end +end + +function StaticList.validate(self, value) + value = (type(value) == "table") and value or {value} + + local valid = {} + for i, v in ipairs(value) do + if luci.util.contains(self.keylist, v) then + table.insert(valid, v) + end + end + return valid +end + + +DynamicList = class(AbstractValue) + +function DynamicList.__init__(self, ...) + AbstractValue.__init__(self, ...) + self.template = "cbi/dynlist" + self.cast = "table" + self.keylist = {} + self.vallist = {} +end + +function DynamicList.reset_values(self) + self.keylist = {} + self.vallist = {} +end + +function DynamicList.value(self, key, val) + val = val or key + table.insert(self.keylist, tostring(key)) + table.insert(self.vallist, tostring(val)) +end + +function DynamicList.write(self, section, value) + local t = { } + + if type(value) == "table" then + local x + for _, x in ipairs(value) do + if x and #x > 0 then + t[#t+1] = x + end + end + else + t = { value } + end + + if self.cast == "string" then + value = table.concat(t, " ") + else + value = t + end + + return AbstractValue.write(self, section, value) +end + +function DynamicList.cfgvalue(self, section) + local value = AbstractValue.cfgvalue(self, section) + + if type(value) == "string" then + local x + local t = { } + for x in value:gmatch("%S+") do + if #x > 0 then + t[#t+1] = x + end + end + value = t + end + + return value +end + +function DynamicList.formvalue(self, section) + local value = AbstractValue.formvalue(self, section) + + if type(value) == "string" then + if self.cast == "string" then + local x + local t = { } + for x in value:gmatch("%S+") do + t[#t+1] = x + end + value = t + else + value = { value } + end + end + + return value +end + + +--[[ +TextValue - A multi-line value + rows: Rows +]]-- +TextValue = class(AbstractValue) + +function TextValue.__init__(self, ...) + AbstractValue.__init__(self, ...) + self.template = "cbi/tvalue" +end + +--[[ +Button +]]-- +Button = class(AbstractValue) + +function Button.__init__(self, ...) + AbstractValue.__init__(self, ...) + self.template = "cbi/button" + self.inputstyle = nil + self.rmempty = true +end + + +FileUpload = class(AbstractValue) + +function FileUpload.__init__(self, ...) + AbstractValue.__init__(self, ...) + self.template = "cbi/upload" + if not self.map.upload_fields then + self.map.upload_fields = { self } + else + self.map.upload_fields[#self.map.upload_fields+1] = self + end +end + +function FileUpload.formcreated(self, section) + return AbstractValue.formcreated(self, section) or + self.map:formvalue("cbi.rlf."..section.."."..self.option) or + self.map:formvalue("cbi.rlf."..section.."."..self.option..".x") +end + +function FileUpload.cfgvalue(self, section) + local val = AbstractValue.cfgvalue(self, section) + if val and fs.access(val) then + return val + end + return nil +end + +function FileUpload.formvalue(self, section) + local val = AbstractValue.formvalue(self, section) + if val then + if not self.map:formvalue("cbi.rlf."..section.."."..self.option) and + not self.map:formvalue("cbi.rlf."..section.."."..self.option..".x") + then + return val + end + fs.unlink(val) + self.value = nil + end + return nil +end + +function FileUpload.remove(self, section) + local val = AbstractValue.formvalue(self, section) + if val and fs.access(val) then fs.unlink(val) end + return AbstractValue.remove(self, section) +end + + +FileBrowser = class(AbstractValue) + +function FileBrowser.__init__(self, ...) + AbstractValue.__init__(self, ...) + self.template = "cbi/browser" +end diff --git a/Mi_Lua/luci/cbi/datatypes.lua b/Mi_Lua/luci/cbi/datatypes.lua new file mode 100644 index 0000000..ce031d0 --- /dev/null +++ b/Mi_Lua/luci/cbi/datatypes.lua @@ -0,0 +1,345 @@ +--[[ + +LuCI - Configuration Bind Interface - Datatype Tests +(c) 2010 Jo-Philipp Wich + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +$Id: datatypes.lua 9352 2012-10-06 23:50:52Z jow $ + +]]-- + +local fs = require "nixio.fs" +local ip = require "luci.ip" +local math = require "math" +local util = require "luci.util" +local tonumber, tostring, type, unpack, select = tonumber, tostring, type, unpack, select + + +module "luci.cbi.datatypes" + + +_M['or'] = function(v, ...) + local i + for i = 1, select('#', ...), 2 do + local f = select(i, ...) + local a = select(i+1, ...) + if type(f) ~= "function" then + if f == v then + return true + end + i = i - 1 + elseif f(v, unpack(a)) then + return true + end + end + return false +end + +_M['and'] = function(v, ...) + local i + for i = 1, select('#', ...), 2 do + local f = select(i, ...) + local a = select(i+1, ...) + if type(f) ~= "function" then + if f ~= v then + return false + end + i = i - 1 + elseif not f(v, unpack(a)) then + return false + end + end + return true +end + +function neg(v, ...) + return _M['or'](v:gsub("^%s*!%s*", ""), ...) +end + +function list(v, subvalidator, subargs) + if type(subvalidator) ~= "function" then + return false + end + local token + for token in v:gmatch("%S+") do + if not subvalidator(token, unpack(subargs)) then + return false + end + end + return true +end + +function bool(val) + if val == "1" or val == "yes" or val == "on" or val == "true" then + return true + elseif val == "0" or val == "no" or val == "off" or val == "false" then + return true + elseif val == "" or val == nil then + return true + end + + return false +end + +function uinteger(val) + local n = tonumber(val) + if n ~= nil and math.floor(n) == n and n >= 0 then + return true + end + + return false +end + +function integer(val) + local n = tonumber(val) + if n ~= nil and math.floor(n) == n then + return true + end + + return false +end + +function ufloat(val) + local n = tonumber(val) + return ( n ~= nil and n >= 0 ) +end + +function float(val) + return ( tonumber(val) ~= nil ) +end + +function ipaddr(val) + return ip4addr(val) or ip6addr(val) +end + +function ip4addr(val) + if val then + return ip.IPv4(val) and true or false + end + + return false +end + +function ip4prefix(val) + val = tonumber(val) + return ( val and val >= 0 and val <= 32 ) +end + +function ip6addr(val) + if val then + return ip.IPv6(val) and true or false + end + + return false +end + +function ip6prefix(val) + val = tonumber(val) + return ( val and val >= 0 and val <= 128 ) +end + +function port(val) + val = tonumber(val) + return ( val and val >= 0 and val <= 65535 ) +end + +function portrange(val) + local p1, p2 = val:match("^(%d+)%-(%d+)$") + if p1 and p2 and port(p1) and port(p2) then + return true + else + return port(val) + end +end + +function macaddr(val) + if val and val:match( + "^[a-fA-F0-9]+:[a-fA-F0-9]+:[a-fA-F0-9]+:" .. + "[a-fA-F0-9]+:[a-fA-F0-9]+:[a-fA-F0-9]+$" + ) then + local parts = util.split( val, ":" ) + + for i = 1,6 do + parts[i] = tonumber( parts[i], 16 ) + if parts[i] < 0 or parts[i] > 255 then + return false + end + end + + return true + end + + return false +end + +function hostname(val) + if val and (#val < 254) and ( + val:match("^[a-zA-Z_]+$") or + (val:match("^[a-zA-Z0-9_][a-zA-Z0-9_%-%.]*[a-zA-Z0-9]$") and + val:match("[^0-9%.]")) + ) then + return true + end + return false +end + +function host(val) + return hostname(val) or ipaddr(val) +end + +function network(val) + return uciname(val) or host(val) +end + +function wpakey(val) + if #val == 64 then + return (val:match("^[a-fA-F0-9]+$") ~= nil) + else + return (#val >= 8) and (#val <= 63) + end +end + +function wepkey(val) + if val:sub(1, 2) == "s:" then + val = val:sub(3) + end + + if (#val == 10) or (#val == 26) then + return (val:match("^[a-fA-F0-9]+$") ~= nil) + else + return (#val == 5) or (#val == 13) + end +end + +function string(val) + return true -- Everything qualifies as valid string +end + +function directory( val, seen ) + local s = fs.stat(val) + seen = seen or { } + + if s and not seen[s.ino] then + seen[s.ino] = true + if s.type == "dir" then + return true + elseif s.type == "lnk" then + return directory( fs.readlink(val), seen ) + end + end + + return false +end + +function file( val, seen ) + local s = fs.stat(val) + seen = seen or { } + + if s and not seen[s.ino] then + seen[s.ino] = true + if s.type == "reg" then + return true + elseif s.type == "lnk" then + return file( fs.readlink(val), seen ) + end + end + + return false +end + +function device( val, seen ) + local s = fs.stat(val) + seen = seen or { } + + if s and not seen[s.ino] then + seen[s.ino] = true + if s.type == "chr" or s.type == "blk" then + return true + elseif s.type == "lnk" then + return device( fs.readlink(val), seen ) + end + end + + return false +end + +function uciname(val) + return (val:match("^[a-zA-Z0-9_]+$") ~= nil) +end + +function range(val, min, max) + val = tonumber(val) + min = tonumber(min) + max = tonumber(max) + + if val ~= nil and min ~= nil and max ~= nil then + return ((val >= min) and (val <= max)) + end + + return false +end + +function min(val, min) + val = tonumber(val) + min = tonumber(min) + + if val ~= nil and min ~= nil then + return (val >= min) + end + + return false +end + +function max(val, max) + val = tonumber(val) + max = tonumber(max) + + if val ~= nil and max ~= nil then + return (val <= max) + end + + return false +end + +function rangelength(val, min, max) + val = tostring(val) + min = tonumber(min) + max = tonumber(max) + + if val ~= nil and min ~= nil and max ~= nil then + return ((#val >= min) and (#val <= max)) + end + + return false +end + +function minlength(val, min) + val = tostring(val) + min = tonumber(min) + + if val ~= nil and min ~= nil then + return (#val >= min) + end + + return false +end + +function maxlength(val, max) + val = tostring(val) + max = tonumber(max) + + if val ~= nil and max ~= nil then + return (#val <= max) + end + + return false +end + +function phonedigit(val) + return (val:match("^[0-9\*#]+$") ~= nil) +end diff --git a/Mi_Lua/luci/ccache.lua b/Mi_Lua/luci/ccache.lua new file mode 100644 index 0000000..56ccbc3 --- /dev/null +++ b/Mi_Lua/luci/ccache.lua @@ -0,0 +1,87 @@ +--[[ +LuCI - Lua Configuration Interface + +Copyright 2008 Steven Barth +Copyright 2008 Jo-Philipp Wich + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +$Id$ +]]-- + +local io = require "io" +local fs = require "nixio.fs" +local util = require "luci.util" +local nixio = require "nixio" +local debug = require "debug" +local string = require "string" +local package = require "package" + +local type, loadfile = type, loadfile + + +module "luci.ccache" + +function cache_ondemand(...) + if debug.getinfo(1, 'S').source ~= "=?" then + cache_enable(...) + end +end + +function cache_enable(cachepath, mode) + cachepath = cachepath or "/tmp/luci-modulecache" + mode = mode or "r--r--r--" + + local loader = package.loaders[2] + local uid = nixio.getuid() + + if not fs.stat(cachepath) then + fs.mkdir(cachepath) + end + + local function _encode_filename(name) + local encoded = "" + for i=1, #name do + encoded = encoded .. ("%2X" % string.byte(name, i)) + end + return encoded + end + + local function _load_sane(file) + local stat = fs.stat(file) + if stat and stat.uid == uid and stat.modestr == mode then + return loadfile(file) + end + end + + local function _write_sane(file, func) + if nixio.getuid() == uid then + local fp = io.open(file, "w") + if fp then + fp:write(util.get_bytecode(func)) + fp:close() + fs.chmod(file, mode) + end + end + end + + package.loaders[2] = function(mod) + local encoded = cachepath .. "/" .. _encode_filename(mod) + local modcons = _load_sane(encoded) + + if modcons then + return modcons + end + + -- No cachefile + modcons = loader(mod) + if type(modcons) == "function" then + _write_sane(encoded, modcons) + end + return modcons + end +end diff --git a/Mi_Lua/luci/config.lua b/Mi_Lua/luci/config.lua new file mode 100644 index 0000000..bc3d533 --- /dev/null +++ b/Mi_Lua/luci/config.lua @@ -0,0 +1,42 @@ +--[[ +LuCI - Configuration + +Description: +Some LuCI configuration values read from uci file "luci" + + +FileId: +$Id: config.lua 3856 2008-12-05 15:36:44Z Cyrus $ + +License: +Copyright 2008 Steven Barth + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +]]-- + +local util = require "luci.util" +module("luci.config", +function(m) + if pcall(require, "luci.model.uci") then + local config = util.threadlocal() + setmetatable(m, { + __index = function(tbl, key) + if not config[key] then + config[key] = luci.model.uci.cursor():get_all("luci", key) + end + return config[key] + end + }) + end +end) diff --git a/Mi_Lua/luci/controller/api/index.lua b/Mi_Lua/luci/controller/api/index.lua new file mode 100644 index 0000000..8b39426 --- /dev/null +++ b/Mi_Lua/luci/controller/api/index.lua @@ -0,0 +1,10 @@ +module("luci.controller.api.index", package.seeall) +function index() + local page = node("api") + page.target = firstchild() + page.title = _("") + page.order = 10 + page.sysauth = "admin" + page.sysauth_authenticator = "jsonauth" + page.index = true +end diff --git a/Mi_Lua/luci/controller/api/xqdatacenter.lua b/Mi_Lua/luci/controller/api/xqdatacenter.lua new file mode 100644 index 0000000..b4d98b6 --- /dev/null +++ b/Mi_Lua/luci/controller/api/xqdatacenter.lua @@ -0,0 +1,336 @@ +module("luci.controller.api.xqdatacenter", package.seeall) + +function index() + local page = node("api","xqdatacenter") + page.target = firstchild() + page.title = ("") + page.order = 300 + page.sysauth = "admin" + page.sysauth_authenticator = "jsonauth" + page.index = true + entry({"api", "xqdatacenter"}, firstchild(), _(""), 300) + entry({"api", "xqdatacenter", "request"}, call("tunnelRequest"), _(""), 301) + entry({"api", "xqdatacenter", "identify_device"}, call("identifyDevice"), _(""), 302, 0x08) + entry({"api", "xqdatacenter", "download"}, call("download"), _(""), 303) + entry({"api", "xqdatacenter", "upload"}, call("upload"), _(""), 304) + entry({"api", "xqdatacenter", "thumb"}, call("getThumb"), _(""), 305) + entry({"api", "xqdatacenter", "device_id"}, call("getDeviceId"), _(""), 306) + entry({"api", "xqdatacenter", "check_file_exist"}, call("checkFileExist"), _(""), 307) + entry({"api", "xqdatacenter", "plugin_ssh"}, call("pluginSSH"), _(""), 308) + entry({"api", "xqdatacenter", "plugin_ssh_status"}, call("pluginSSHStatus"), _(""), 309) +end + +local LuciHttp = require("luci.http") +local LuciJson = require("json") +local XQConfigs = require("xiaoqiang.common.XQConfigs") +local XQFunction = require("xiaoqiang.common.XQFunction") +local XQErrorUtil = require("xiaoqiang.util.XQErrorUtil") + +function tunnelRequest() + local XQCryptoUtil = require("xiaoqiang.util.XQCryptoUtil") + local payload = XQCryptoUtil.binaryBase64Enc(LuciHttp.formvalue("payload")) + local cmd = XQConfigs.THRIFT_TUNNEL_TO_DATACENTER % payload + local LuciUtil = require("luci.util") + LuciHttp.write(LuciUtil.exec(cmd)) +end + +function identifyDevice() + local cmd = XQConfigs.THRIFT_TO_MQTT_IDENTIFY_DEVICE + local LuciUtil = require("luci.util") + local result = {} + result["code"] = 0 + result["info"] = LuciUtil.exec(cmd) + LuciHttp.write_json(result) +end + +function getDeviceId() + local cmd = XQConfigs.THRIFT_TO_MQTT_GET_DEVICEID + local LuciUtil = require("luci.util") + local result = {} + result["code"] = 0 + result["deviceId"] = LuciUtil.exec(cmd) + LuciHttp.write_json(result) +end + +function download() + local fs = require("nixio.fs") + local mime = require("luci.http.protocol.mime") + local ltn12 = require("luci.ltn12") + local log = require("xiaoqiang.XQLog") + + local path = LuciHttp.formvalue("path") + if XQFunction.isStrNil(path) then + LuciHttp.status(404, _("no Such file")) + return + end + + local constPrefix1 = "/userdisk/data/" + local constPrefix2 = "/extdisks/" + local constPrefix3 = "/userdisk/privacyData/" + if (string.sub(path, 1, string.len(constPrefix1)) ~= constPrefix1) and (string.sub(path, 1, string.len(constPrefix2)) ~= constPrefix2) and (string.sub(path, 1, string.len(constPrefix3)) ~= constPrefix3) then + LuciHttp.status(403, _("no permission")) + return + end + + -- check privacy disk permission by md5 device mac address + --[[if string.sub(path, 1, string.len(constPrefix3)) == constPrefix3 then + local secret = LuciHttp.formvalue("secret") + if XQFunction.isStrNil(secret) then + LuciHttp.status(403, _("no permission")) + return + end + + log.log(3, "=============secret = " .. secret) + + local access = false + local XQCryptoUtil = require("xiaoqiang.util.XQCryptoUtil") + local XQDeviceUtil = require("xiaoqiang.util.XQDeviceUtil") + local macfilterInfoList = XQDeviceUtil.getMacfilterInfoList() + for _,value in ipairs(macfilterInfoList) do + if XQFunction.isStrNil(value.mac) == false then + log.log(3, "=============mac = " .. value.mac) + if string.lower(XQCryptoUtil.md5Str(string.lower(value.mac))) == string.lower(secret) then + log.log(3, "=============device found") + if value.pridisk then + access = true + end + break + end + end + end + if access == false then + LuciHttp.status(403, _("no permission")) + return + end + end]] + + log.log(3, "=============path = " .. path) + local stat = fs.stat(path) + if not stat then + LuciHttp.status(404, _("no Such file")) + return + end + + LuciHttp.header("Accept-Ranges", "bytes") + LuciHttp.header("Content-Type", mime.to_mime(path)) + local range = LuciHttp.getenv("HTTP_RANGE") + -- format: bytes=123- + if range then + LuciHttp.status(206) + range = string.gsub(range, "bytes=", "") + range = string.gsub(range, "-", "") + else + range = 0 + end + log.log(3, "=============range = " .. range) + -- format: bytes 123-456/457 + local contentRange = "bytes " .. range .. "-" .. (stat.size - 1) .. "/" .. stat.size + log.log(3, "=============contentRange = " .. contentRange) + LuciHttp.header("Content-Length", stat.size - range) + LuciHttp.header("Content-Range", contentRange) + LuciHttp.header("Content-Disposition", "attachment; filename=" .. fs.basename(path)) + + if string.sub(path, 1, string.len(constPrefix1)) == constPrefix1 then + LuciHttp.header("X-Accel-Redirect", "/download-userdisk/" .. string.sub(path, string.len(constPrefix1) + 1, string.len(path))) + elseif string.sub(path, 1, string.len(constPrefix2)) == constPrefix2 then + LuciHttp.header("X-Accel-Redirect", "/download-extdisks/" .. string.sub(path, string.len(constPrefix2) + 1, string.len(path))) + elseif string.sub(path, 1, string.len(constPrefix3)) == constPrefix3 then + LuciHttp.header("X-Accel-Redirect", "/download-pridisk/" .. string.sub(path, string.len(constPrefix3) + 1, string.len(path))) + end + + --local file = io.open(path, "r") + --local position = file:seek("set", range) + --log.log(3, "=============position = " .. position) + --ltn12.pump.all(ltn12.source.file(file), LuciHttp.write) +end + +function upload() + local fp + local log = require("xiaoqiang.XQLog") + local fs = require("luci.fs") + local tmpfile = "/userdisk/upload.tmp" + if fs.isfile(tmpfile) then + fs.unlink(tmpfile) + end + local filename + LuciHttp.setfilehandler( + function(meta, chunk, eof) + if not fp then + if meta and meta.name == "file" then + fp = io.open(tmpfile, "w") + filename = meta.file + filename = string.gsub(filename, "+", " ") + filename = string.gsub(filename, "%%(%x%x)", + function(h) + return string.char(tonumber(h, 16)) + end) + filename = filename.gsub(filename, "\r\n", "\n") + end + end + if chunk then + fp:write(chunk) + end + if eof then + fp:close() + end + end + ) + + local path = LuciHttp.formvalue("target") + if string.match(path, "\/$") == nil then + path = path .. "/" + end + fs.mkdir(path, true) + + local savename = filename + if fs.isfile(path .. savename) then + local basename = savename + local index = basename:match(".+()%.%w+$") + if index then + basename = basename:sub(1, index - 1) + end + local extension = savename:match(".+%.(%w+)$") + for i = 1, 100, 1 do + local tmpname = basename .. "(" .. i .. ")" + if extension then + tmpname = tmpname .. "." .. extension + end + if not fs.isfile(path .. tmpname) then + savename = tmpname + break + end + end + end + + local dest = path .. savename + log.log(3, "dest=" .. dest) + fs.rename(tmpfile, dest) + + local result = {} + result["code"] = 0 + LuciHttp.write_json(result) +end + +function getThumb() + local LuciUtil = require("luci.util") + local fs = require("nixio.fs") + local mime = require("luci.http.protocol.mime") + local ltn12 = require("luci.ltn12") + local log = require("xiaoqiang.XQLog") + + local realPath = LuciHttp.formvalue("filePath") + log.log(3, "realPath = ", realPath) + if (realPath == nil) then + LuciHttp.status(404, _("no Such file")) + return + end + + local XQCryptoUtil = require("xiaoqiang.util.XQCryptoUtil") + local payload = "{\"api\":10, \"files\":[\"" ..realPath.. "\"]}" + local thumbResponse = XQFunction.thrift_tunnel_to_datacenter(payload) + if thumbResponse and thumbResponse.code == 0 then + local thumbPath = thumbResponse.thumbnails[1] + local stat = fs.stat(thumbPath) + LuciHttp.header("Content-Type", mime.to_mime(thumbPath)) + LuciHttp.header("Content-Length", stat.size) + ltn12.pump.all(ltn12.source.file(io.open(thumbPath, "r")), LuciHttp.write) + else + LuciHttp.status(404, _("no Such thumb file")) + end +end + +function checkFileExist() + local fs = require("nixio.fs") + + local exist = true + local path = LuciHttp.formvalue("filePath") + if XQFunction.isStrNil(path) then + exist = false + else + local stat = fs.stat(path) + if not stat then + exist = false + end + end + + local result = {} + result["code"] = 0 + result['exist'] = exist + LuciHttp.write_json(result) +end + +function pluginSSH() + local LuciUtil = require("luci.util") + local XQLog = require("xiaoqiang.XQLog") + local code = 0 + local result = {} + local pluginID = LuciHttp.formvalue("pluginID") + local capabilitystr = LuciHttp.formvalue("capability") + local open = tonumber(LuciHttp.formvalue("open") or 0) + XQLog.check(0, XQLog.KEY_FUNC_PLUGIN, 1) + if open and open == 1 then + if pluginID and capabilitystr then + local payload = { + ["api"] = 611, + ["pluginID"] = pluginID, + ["capability"] = LuciUtil.split(capabilitystr, ",") + } + local datacenter = XQFunction.thrift_tunnel_to_datacenter(LuciJson.encode(payload)) + if datacenter and datacenter.code ~= 0 then + code = 1595 + end + else + code = 1537 + end + else + local payload = { + ["api"] = 613 + } + local datacenter = XQFunction.thrift_tunnel_to_datacenter(LuciJson.encode(payload)) + if datacenter and datacenter.code == 0 then + code = 0 + else + code = 1601 + end + end + if code ~= 0 then + result["msg"] = XQErrorUtil.getErrorMessage(code) + end + result["code"] = code + LuciHttp.write_json(result) +end + +function pluginSSHStatus() + local code = 0 + local result = {} + local datacenter = XQFunction.thrift_tunnel_to_datacenter([[{"api":612}]]) + local capability = XQFunction.thrift_tunnel_to_datacenter([[{"api":621}]]) + if datacenter and datacenter.code == 0 and capability and datacenter.code == 0 then + local capabilitylist = {} + result["enable"] = datacenter.status == 1 and 1 or 0 + local encapability = {} + if result.enable == 1 then + local pluginSSH = datacenter.plugin_ssh_status + result["pluginID"] = pluginSSH.pluginID + encapability = pluginSSH.capability + end + for _, item in ipairs(capability.list) do + item.enable = 0 + for _, capa in ipairs(encapability) do + if item.key == capa then + item.enable = 1 + break + end + end + table.insert(capabilitylist, item) + end + result["capability"] = capabilitylist + else + code = 1600 + end + if code ~= 0 then + result["msg"] = XQErrorUtil.getErrorMessage(code) + end + result["code"] = code + LuciHttp.write_json(result) +end diff --git a/Mi_Lua/luci/controller/api/xqnetdetect.lua b/Mi_Lua/luci/controller/api/xqnetdetect.lua new file mode 100644 index 0000000..a1995fa --- /dev/null +++ b/Mi_Lua/luci/controller/api/xqnetdetect.lua @@ -0,0 +1,293 @@ +module("luci.controller.api.xqnetdetect", package.seeall) + +function index() + local page = node("api","xqnetdetect") + page.target = firstchild() + page.title = ("") + page.order = 350 + page.sysauth = "admin" + page.sysauth_authenticator = "jsonauth" + page.index = true + entry({"api", "xqnetdetect"}, firstchild(), _(""), 350) + entry({"api", "xqnetdetect", "wan_status"}, call("getWanStatus"), _(""), 351, 0x01) + entry({"api", "xqnetdetect", "sys_info"}, call("getSysInfo"), (""), 352, 0x01) + entry({"api", "xqnetdetect", "ping_test"}, call("pingTest"), (""), 353, 0x01) + entry({"api", "xqnetdetect", "detect"}, call("systemDiagnostics"), (""), 354, 0x01) + entry({"api", "xqnetdetect", "sys_status"}, call("systemStatus"), (""), 355, 0x01) + entry({"api", "xqnetdetect", "netspeed"}, call("netspeed"), (""), 356) + entry({"api", "xqnetdetect", "uploadspeed"}, call("uploadSpeed"), (""), 357) +end + +local LuciHttp = require("luci.http") +local XQFunction = require("xiaoqiang.common.XQFunction") +local XQErrorUtil = require("xiaoqiang.util.XQErrorUtil") + +function getWanStatus() + local XQLanWanUtil = require("xiaoqiang.util.XQLanWanUtil") + local result = {} + local wanType = XQLanWanUtil.getAutoWanType() + local wanInfo = XQLanWanUtil.getLanWanInfo("wan") + local wanMonitor = XQLanWanUtil.getWanMonitorStat() + result["code"] = 0 + result["wanLink"] = wanType == 99 and 0 or 1 + result["wanType"] = wanType + result["wanInfo"] = wanInfo + result["wanMonitor"] = wanMonitor + LuciHttp.write_json(result) +end + +function getSysInfo() + local LuciSys = require("luci.sys") + local result = {} + local cpu = {} + local mem = {} + local system, model, memtotal, memcached, membuffers, memfree, bogomips = LuciSys.sysinfo() + cpu["info"] = system + mem["total"] = memtotal + mem["free"] = memfree + result["code"] = 0 + result["cpuInfo"] = cpu + result["memInfo"] = mem + LuciHttp.write_json(result) +end + +function pingTest() + local LuciSys = require("luci.sys") + local pingUrl = LuciHttp.formvalue("url") + local ping = LuciSys.net.pingtest(pingUrl) + local result = {} + result["code"] = 0 + result["result"] = ping == 0 and 1 or 0 + LuciHttp.write_json(result) +end + +--[[ + simple : 0/1/2 (正常模式,时间长上传log/简单模式,时间短,不上传log/简单模式,时间短,上传log) +]]-- +function systemDiagnostics() + local XQLog = require("xiaoqiang.XQLog") + local XQSysUtil = require("xiaoqiang.util.XQSysUtil") + local XQSecureUtil = require("xiaoqiang.util.XQSecureUtil") + local XQWifiUtil = require("xiaoqiang.util.XQWifiUtil") + local XQDeviceUtil = require("xiaoqiang.util.XQDeviceUtil") + + local lan = XQDeviceUtil.getWanLanNetworkStatistics("lan") + local wan = XQDeviceUtil.getWanLanNetworkStatistics("wan") + local speed = {} + speed["lan"] = tonumber(lan.downspeed) + speed["wan"] = tonumber(wan.downspeed) + + local simple = tonumber(LuciHttp.formvalue("simple") or 0) + local target = LuciHttp.formvalue("target") + local result = {} + local code = 0 + local status = 0 + local count = 0 + local cpuTemperature = XQSysUtil.getCpuTemperature() + local network = XQSysUtil.getNetworkDetectInfo(simple,target) + XQSysUtil.setDetectionTimestamp() + + local wifiInfo = XQWifiUtil.getAllWifiInfo() + local same = false + local strong = true + local wifi = {} + for i=1, #wifiInfo do + if XQSecureUtil.checkPlaintextPwd("admin", wifiInfo[i].password) then + same = true + end + if XQSecureUtil.checkStrong(wifiInfo[i].password) < 2 then + strong = false + end + end + wifi["same"] = same and 1 or 0 + wifi["strong"] = strong and 1 or 0 + + local disk = {} + local diskinfo = XQFunction.thrift_tunnel_to_datacenter([[{"api":26}]]) + if diskinfo and diskinfo.code == 0 then + local capacity = tonumber(diskinfo.capacity) + local free = tonumber(diskinfo.free) + disk["Used"] = string.format("%.3fG", (capacity - free)/1073741824) + disk["Available"] = string.format("%.3fG", free/1073741824) + end + + if network then + local cputemp = {} + cputemp["temperature"] = cpuTemperature + if cpuTemperature > 70 then + count = count + 1 + status = 1 + cputemp["status"] = 0 + else + cputemp["status"] = 1 + end + local cpuavg = {} + cpuavg["loadavg"] = network.cpu + if tonumber(network.cpu) > 90 then + count = count + 1 + status = 1 + cpuavg["status"] = 0 + else + cpuavg["status"] = 1 + end + local memoryuse = {} + memoryuse["use"] = network.memory + if tonumber(network.memory) > 90 then + count = count + 1 + status = 1 + memoryuse["status"] = 0 + else + memoryuse["status"] = 1 + end + local link = {} + if network.wanLink ~= 1 then + count = count + 1 + status = 2 + link["status"] = 0 + else + link["status"] = 1 + end + local wan = {} + wan["type"] = network.wanType + if tonumber(network.wanLink) ~= 1 then + count = count + 1 + status = 2 + wan["status"] = 0 + else + wan["status"] = 1 + end + local gateway = {} + gateway["lost"] = network.gw + if tonumber(network.gw) > 80 then + count = count + 1 + status = 1 + gateway["status"] = 0 + else + gateway["status"] = 1 + end + local dnsstatus = {} + if tonumber(network.dns) ~= 1 then + count = count + 1 + status = 2 + dnsstatus["status"] = 0 + else + dnsstatus["status"] = 1 + end + local ping = {} + ping["lost"] = network.pingLost + if tonumber(network.pingLost) > 80 then + count = count + 1 + status = 2 + ping["status"] = 0 + else + ping["status"] = 1 + end + result = network + result["count"] = count + result["status"] = status + result["cpuavg"] = cpuavg + result["memoryuse"] = memoryuse + result["cputemp"] = cputemp + result["link"] = link + result["wan"] = wan + result["gateway"] = gateway + result["dnsstatus"] = dnsstatus + result["ping"] = ping + result["cpuTemperature"] = cpuTemperature + result["wifi"] = wifi + result["speed"] = speed + result["disk"] = disk + if count > 0 then + XQLog.check(0, XQLog.KEY_DETECT_ERROR, 1) + end + else + code = 1567 + end + if code ~= 0 then + result["msg"] = XQErrorUtil.getErrorMessage(code) + else + local XQPushHelper = require("xiaoqiang.XQPushHelper") + local LuciJson = require("json") + local payload = { + ["type"] = 6, + ["data"] = { + ["lan"] = speed.lan, + ["wan"] = speed.wan + } + } + XQPushHelper.push_request(LuciJson.encode(payload)) + end + result["code"] = code + LuciHttp.write_json(result) +end + +function systemStatus() + local XQSysUtil = require("xiaoqiang.util.XQSysUtil") + local count = 0 + local result = {} + local status = XQSysUtil.checkSystemStatus() + result["code"] = 0 + result["status"] = 0 + if (status.cpu and status.cpu > 90) then + count = count + 1 + result["status"] = 1 + end + if (status.mem and status.mem > 90) then + count = count + 1 + result["status"] = 1 + end + if (status.tmp and status.tmp > 70) then + count = count + 1 + result["status"] = 1 + end + if not status.wan or not status.link then + count = count + 1 + result["status"] = 2 + end + result["count"] = count + LuciHttp.write_json(result) +end + +function netspeed() + local XQPreference = require("xiaoqiang.XQPreference") + local XQNSTUtil = require("xiaoqiang.module.XQNetworkSpeedTest") + local code = 0 + local result = {} + local history = LuciHttp.formvalue("history") + if history then + result["bandwidth"] = tonumber(XQPreference.get("BANDWIDTH", 0, "xiaoqiang")) + result["download"] = tonumber(string.format("%.2f", 128 * result.bandwidth)) + else + local download = XQNSTUtil.downloadSpeedTest() + if download then + result["download"] = download + result["bandwidth"] = tonumber(string.format("%.2f", 8 * download/1024)) + XQPreference.set("BANDWIDTH", tostring(result.bandwidth), "xiaoqiang") + else + code = 1588 + end + if code ~= 0 then + result["msg"] = XQErrorUtil.getErrorMessage(code) + end + end + result["code"] = code + LuciHttp.write_json(result) +end + +function uploadSpeed() + local XQNSTUtil = require("xiaoqiang.module.XQNetworkSpeedTest") + local code = 0 + local result = {} + local upload = XQNSTUtil.uploadSpeedTest() + if upload then + result["upload"] = upload + result["bandwidth"] = tonumber(string.format("%.2f", 8 * upload/1024)) + else + code = 1588 + end + if code ~= 0 then + result["msg"] = XQErrorUtil.getErrorMessage(code) + end + result["code"] = code + LuciHttp.write_json(result) +end \ No newline at end of file diff --git a/Mi_Lua/luci/controller/api/xqnetwork.lua b/Mi_Lua/luci/controller/api/xqnetwork.lua new file mode 100644 index 0000000..4c7555d --- /dev/null +++ b/Mi_Lua/luci/controller/api/xqnetwork.lua @@ -0,0 +1,1310 @@ +module("luci.controller.api.xqnetwork", package.seeall) + +function index() + local page = node("api","xqnetwork") + page.target = firstchild() + page.title = ("") + page.order = 200 + page.sysauth = "admin" + page.sysauth_authenticator = "jsonauth" + page.index = true + entry({"api", "xqnetwork"}, firstchild(), (""), 200) + entry({"api", "xqnetwork", "wifi_status"}, call("getWifiStatus"), (""), 201) + entry({"api", "xqnetwork", "wifi_detail"}, call("getWifiInfo"), (""), 202) + entry({"api", "xqnetwork", "wifi_detail_all"}, call("getAllWifiInfo"), (""), 202) + entry({"api", "xqnetwork", "wifi_connect_devices"}, call("getWifiConDev"), (""), 203) + entry({"api", "xqnetwork", "wifi_txpwr_channel"}, call("getWifiChTx"), (""), 204) + -- entry({"api", "xqnetwork", "set_wifi_txpwr_channel"}, call("setWifiChTx"), (""), 205) + entry({"api", "xqnetwork", "set_wifi_txpwr"}, call("setWifiTxpwr"), (""), 205) + entry({"api", "xqnetwork", "wifi_up"}, call("turnOnWifi"), (""), 206) + entry({"api", "xqnetwork", "wifi_down"}, call("shutDownWifi"), (""), 207) + entry({"api", "xqnetwork", "set_wifi"}, call("setWifi"), (""), 208) + --entry({"api", "xqnetwork", "get_scan_list"}, call("get_scan_list"), (""), 209, true) + --entry({"api", "xqnetwork", "wifi_ctl_scan"}, call("wifi_ctl_scan"), (""), 210) + --entry({"api", "xqnetwork", "get_bridge"}, call("get_bridge"), (""), 211, true) + --entry({"api", "xqnetwork", "set_bridge"}, call("set_bridge"), (""), 212) + entry({"api", "xqnetwork", "lan_info"}, call("getLanInfo"), (""), 213) + entry({"api", "xqnetwork", "wan_info"}, call("getWanInfo"), (""), 214) + entry({"api", "xqnetwork", "lan_dhcp"}, call("getLanDhcp"), (""), 215) + entry({"api", "xqnetwork", "wan_down"}, call("wanDown"), (""), 216) + entry({"api", "xqnetwork", "wan_up"}, call("wanUp"), (""), 217) + entry({"api", "xqnetwork", "check_wan_type"}, call("getAutoWanType"), (""), 218, 0x08) + entry({"api", "xqnetwork", "wan_statistics"}, call("getWanStatistics"), (""), 219) + -- + entry({"api", "xqnetwork", "devices_statistics"}, call("getDevsStatistics"), (""), 220) + entry({"api", "xqnetwork", "device_statistics"}, call("getDevStatistics"), (""), 221) + -- + entry({"api", "xqnetwork", "set_lan_ip"}, call("setLanIp"), (""), 222) + entry({"api", "xqnetwork", "set_wan"}, call("setWan"), (""), 223, 0x08) + entry({"api", "xqnetwork", "set_lan_dhcp"}, call("setLanDhcp"), (""), 224) + entry({"api", "xqnetwork", "mac_clone"}, call("setWanMac"), (""), 225) + entry({"api", "xqnetwork", "set_all_wifi"}, call("setAllWifi"), (""), 226) + entry({"api", "xqnetwork", "avaliable_channels"}, call("getChannels"), (""), 227) + -- WiFi macfilter + entry({"api", "xqnetwork", "wifi_macfilter_info"}, call("getWifiMacfilterInfo"), (""), 228) + entry({"api", "xqnetwork", "set_wifi_macfilter"}, call("setWifiMacfilter"), (""), 229) + entry({"api", "xqnetwork", "edit_device"}, call("editDevice"), (""), 230) + -- Mac bind + entry({"api", "xqnetwork", "mac_bind"}, call("macBind"), (""), 231) + entry({"api", "xqnetwork", "mac_unbind"}, call("macUnbind"), (""), 232) + entry({"api", "xqnetwork", "savebind"}, call("saveBind"), (""), 233) + entry({"api", "xqnetwork", "unbindall"}, call("unbindAll"), (""), 234) + entry({"api", "xqnetwork", "macbind_info"}, call("getMacBindInfo"), (""), 235) + -- PPPoE + entry({"api", "xqnetwork", "pppoe_status"}, call("pppoeStatus"), (""), 236) + entry({"api", "xqnetwork", "pppoe_stop"}, call("pppoeStop"), (""), 237) + entry({"api", "xqnetwork", "pppoe_start"}, call("pppoeStart"), (""), 238) + -- QoS + entry({"api", "xqnetwork", "qos_info"}, call("getQosInfo"), (""), 239) + entry({"api", "xqnetwork", "qos_switch"}, call("qosSwitch"), (""), 240) + entry({"api", "xqnetwork", "qos_mode"}, call("qosMode"), (""), 241) + entry({"api", "xqnetwork", "qos_limit"}, call("qosLimit"), (""), 242) + entry({"api", "xqnetwork", "qos_offlimit"}, call("qosOffLimit"), (""), 243) + entry({"api", "xqnetwork", "set_band"}, call("setBand"), (""), 244) + -- NAT + entry({"api", "xqnetwork", "portforward"}, call("portForward"), (""), 245) + entry({"api", "xqnetwork", "add_redirect"}, call("addRedirect"), (""), 246) + entry({"api", "xqnetwork", "add_range_redirect"}, call("addRangeRedirect"), (""), 247) + entry({"api", "xqnetwork", "delete_redirect"}, call("deleteRedirect"), (""), 248) + entry({"api", "xqnetwork", "redirect_apply"}, call("redirectApply"), (""), 249) + -- DMZ + entry({"api", "xqnetwork", "dmz"}, call("getDMZInfo"), (""), 250) + entry({"api", "xqnetwork", "set_dmz"}, call("setDMZ"), (""), 251) + entry({"api", "xqnetwork", "dmz_off"}, call("closeDMZ"), (""), 252) + entry({"api", "xqnetwork", "dmz_reload"}, call("reloadDMZ"), (""), 252) + -- DDNS + entry({"api", "xqnetwork", "ddns"}, call("ddnsStatus"), (""), 253) + entry({"api", "xqnetwork", "ddns_switch"}, call("ddnsSwitch"), (""), 254) + entry({"api", "xqnetwork", "add_server"}, call("addServer"), (""), 255) + entry({"api", "xqnetwork", "del_server"}, call("deleteServer"), (""), 256) + entry({"api", "xqnetwork", "server_switch"}, call("serverSwitch"), (""), 258) + entry({"api", "xqnetwork", "ddns_reload"}, call("ddnsReload"), (""), 259) + entry({"api", "xqnetwork", "ddns_edit"}, call("ddnsEdit"), (""), 260) + entry({"api", "xqnetwork", "get_server"}, call("getServer"), (""), 261) +end + +local LuciHttp = require("luci.http") +local XQErrorUtil = require("xiaoqiang.util.XQErrorUtil") + +function getWifiStatus() + local XQWifiUtil = require("xiaoqiang.util.XQWifiUtil") + local result = {} + local status = {} + table.insert(status,XQWifiUtil.getWifiStatus(1)) + table.insert(status,XQWifiUtil.getWifiStatus(2)) + result["code"] = 0 + result["status"] = status + LuciHttp.write_json(result) +end + +function getWifiInfo() + local XQWifiUtil = require("xiaoqiang.util.XQWifiUtil") + local result = {} + local code = 0 + local index = tonumber(LuciHttp.formvalue("wifiIndex")) + if index and index < 3 then + result["info"] = XQWifiUtil.getAllWifiInfo()[index] + else + code = 1523 + end + if code ~= 0 then + result["msg"] = XQErrorUtil.getErrorMessage(code) + end + result["code"] = code + LuciHttp.write_json(result) +end + +function getAllWifiInfo() + local XQWifiUtil = require("xiaoqiang.util.XQWifiUtil") + local result = {} + local code = 0 + result["info"] = XQWifiUtil.getAllWifiInfo() + result["code"] = code + LuciHttp.write_json(result) +end + +function getWifiConDev() + local XQWifiUtil = require("xiaoqiang.util.XQWifiUtil") + local result = {} + result["code"] = 0 + result["list"] = XQWifiUtil.getAllWifiConnetDeviceList() + LuciHttp.write_json(result) +end + +function getWifiChTx() + local XQWifiUtil = require("xiaoqiang.util.XQWifiUtil") + local result = {} + result["code"] = 0 + result["list"] = XQWifiUtil.getWifiChannelTxpwrList() + LuciHttp.write_json(result) +end + +function setWifiChTx() + local XQWifiUtil = require("xiaoqiang.util.XQWifiUtil") + local XQFunction = require("xiaoqiang.common.XQFunction") + local result = {} + local code = 0 + local channel1 = LuciHttp.formvalue("channel1") + local txpwr1 = LuciHttp.formvalue("txpwr1") + local channel2 = LuciHttp.formvalue("channel2") + local txpwr2 = LuciHttp.formvalue("txpwr2") + if XQFunction.isStrNil(channel1) and XQFunction.isStrNil(channel2) and XQFunction.isStrNil(txpwr1) and XQFunction.isStrNil(txpwr2) then + code = 1502 + else + XQWifiUtil.setWifiChannelTxpwr(channel1,txpwr1,channel2,txpwr2) + end + if code ~= 0 then + result["msg"] = XQErrorUtil.getErrorMessage(code) + end + result["code"] = code + LuciHttp.write_json(result) + if code == 0 then + LuciHttp.close() + XQFunction.forkRestartWifi() + end +end + +function setWifiTxpwr() + local XQWifiUtil = require("xiaoqiang.util.XQWifiUtil") + local XQFunction = require("xiaoqiang.common.XQFunction") + local result = {} + local code = 0 + local txpwr = LuciHttp.formvalue("txpwr") + if XQFunction.isStrNil(txpwr) then + code = 1502 + else + XQWifiUtil.setWifiTxpwr(txpwr) + end + if code ~= 0 then + result["msg"] = XQErrorUtil.getErrorMessage(code) + end + result["code"] = code + LuciHttp.write_json(result) + if code == 0 then + LuciHttp.close() + XQFunction.forkRestartWifi() + end +end + +function turnOnWifi() + local XQWifiUtil = require("xiaoqiang.util.XQWifiUtil") + local result = {} + local code = 0 + local index = tonumber(LuciHttp.formvalue("wifiIndex")) + if index and index < 3 then + XQWifiUtil.turnWifiOn(index) + else + code = 1523 + end + if code ~= 0 then + result["msg"] = XQErrorUtil.getErrorMessage(code) + end + result["code"] = code + LuciHttp.write_json(result) +end + +function shutDownWifi() + local XQWifiUtil = require("xiaoqiang.util.XQWifiUtil") + local result = {} + local code = 0 + local index = tonumber(LuciHttp.formvalue("wifiIndex")) + if index and index < 3 then + XQWifiUtil.turnWifiOff(index) + else + code = 1523 + end + if code ~= 0 then + result["msg"] = XQErrorUtil.getErrorMessage(code) + end + result["code"] = code + LuciHttp.write_json(result) +end + +function setWifi() + local XQLog = require("xiaoqiang.XQLog") + local XQWifiUtil = require("xiaoqiang.util.XQWifiUtil") + local XQFunction = require("xiaoqiang.common.XQFunction") + local result = {} + local code = 0 + local index = tonumber(LuciHttp.formvalue("wifiIndex")) + local ssid = LuciHttp.formvalue("ssid") + local password = LuciHttp.formvalue("pwd") + local encryption = LuciHttp.formvalue("encryption") + local channel = LuciHttp.formvalue("channel") + local bandwidth = LuciHttp.formvalue("bandwidth") + local txpwr = LuciHttp.formvalue("txpwr") + local hidden = LuciHttp.formvalue("hidden") + local on = LuciHttp.formvalue("on") + if on ~= nil then + on = tonumber(on) + end + if channel == "0" then + bandwidth = "0" + end + if index == 1 then + if channel then + XQLog.check(0, XQLog.KEY_FUNC_2G_CHANNEL, channel) + end + if txpwr then + XQLog.check(0, XQLog.KEY_FUNC_2G_SIGNAL, txpwr) + end + elseif index == 2 then + if channel then + XQLog.check(0, XQLog.KEY_FUNC_5G_CHANNEL, channel) + end + if txpwr then + XQLog.check(0, XQLog.KEY_FUNC_5G_SIGNAL, txpwr) + end + elseif index == 3 then + -- todo: Guest wifi + end + local wifirestart = true + code = XQWifiUtil.checkSSID(ssid,31) + if code == 0 then + if index == 1 or index == 2 then + local succeed = XQWifiUtil.setWifiBasicInfo(index, ssid, password, encryption, channel, txpwr, hidden, on, bandwidth) + if succeed == false then + code = XQWifiUtil.checkWifiPasswd(password,encryption) + end + elseif index == 3 then + local XQGuestWifi = require("xiaoqiang.module.XQGuestWifi") + local succeed = XQGuestWifi.setGuestWifi(1, ssid, encryption, password, 1, on) + if succeed == false then + code = 1615 + else + wifirestart = false + end + end + end + if code ~= 0 then + result["msg"] = XQErrorUtil.getErrorMessage(code) + end + result["code"] = code + LuciHttp.write_json(result) + if code == 0 then + LuciHttp.close() + if wifirestart then + XQFunction.forkRestartWifi() + end + end +end + +function setAllWifi() + local XQSysUtil = require("xiaoqiang.util.XQSysUtil") + local XQWifiUtil = require("xiaoqiang.util.XQWifiUtil") + local XQFunction = require("xiaoqiang.common.XQFunction") + local result = {} + local code = 0 + local code1 = 0 + local code2 = 0 + local details = {} + local on1 = LuciHttp.formvalue("on1") + local ssid1 = LuciHttp.formvalue("ssid1") + local password1 = LuciHttp.formvalue("pwd1") + local encryption1 = LuciHttp.formvalue("encryption1") + local channel1 = LuciHttp.formvalue("channel1") + local txpwr1 = LuciHttp.formvalue("txpwr1") + local hidden1 = LuciHttp.formvalue("hidden1") + local bandwidth1 = LuciHttp.formvalue("bandwidth1") + + local on2 = LuciHttp.formvalue("on2") + local ssid2 = LuciHttp.formvalue("ssid2") + local password2 = LuciHttp.formvalue("pwd2") + local encryption2 = LuciHttp.formvalue("encryption2") + local channel2 = LuciHttp.formvalue("channel2") + local txpwr2 = LuciHttp.formvalue("txpwr2") + local hidden2 = LuciHttp.formvalue("hidden2") + local bandwidth2 = LuciHttp.formvalue("bandwidth2") + + local on3 = LuciHttp.formvalue("on3") + local ssid3 = LuciHttp.formvalue("ssid3") + local password3 = LuciHttp.formvalue("pwd3") + local encryption3 = LuciHttp.formvalue("encryption3") + + if on1 ~= nil then + on1 = tonumber(on1) + end + if on2 ~= nil then + on2 = tonumber(on2) + end + if channel1 == "0" then + bandwidth1 = "0" + end + if channel2 == "0" then + bandwidth2 = "0" + end + local code1 = XQWifiUtil.checkSSID(ssid1,31) + local code2 = XQWifiUtil.checkSSID(ssid2,31) + if on1 ~= 0 and not XQFunction.isStrNil(ssid1) then + XQSysUtil.setRouterName(ssid1) + end + local succeed + if on1 == 0 then + succeed = XQWifiUtil.setWifiBasicInfo(1, nil, nil, nil, nil, nil, nil, on1, nil) + else + code1 = XQWifiUtil.checkSSID(ssid1,31) + if code1 == 0 then + succeed = XQWifiUtil.setWifiBasicInfo(1, ssid1, password1, encryption1, channel1, txpwr1, hidden1, on1, bandwidth1) + else + code = code1 + end + end + if succeed == false then + local error1 = {} + code1 = XQWifiUtil.checkWifiPasswd(password1,encryption1) + error1["code"] = code1 + error1["msg"] = XQErrorUtil.getErrorMessage(code1) + table.insert(details,error1) + end + if on2 == 0 then + succeed = XQWifiUtil.setWifiBasicInfo(2, nil, nil, nil, nil, nil, nil, on2, nil) + else + code2 = XQWifiUtil.checkSSID(ssid2,31) + if code2 == 0 then + succeed = XQWifiUtil.setWifiBasicInfo(2, ssid2, password2, encryption2, channel2, txpwr2, hidden2, on2, bandwidth2) + else + code = code2 + end + end + if succeed == false then + local error2 = {} + code2 = XQWifiUtil.checkWifiPasswd(password2,encryption2) + error2["code"] = code2 + error2["msg"] = XQErrorUtil.getErrorMessage(code2) + table.insert(details,error2) + end + if code1+code2 > 0 and code == 0 then + code = 1516 + end + local wifirestart = true + if on3 then + local XQGuestWifi = require("xiaoqiang.module.XQGuestWifi") + if not XQGuestWifi.setGuestWifi(1, ssid3, encryption3, password3, 1, on3) then + code = 1615 + else + wifirestart = false + end + end + if code ~= 0 then + result["msg"] = XQErrorUtil.getErrorMessage(code) + result["errorDetails"] = details + end + result["code"] = code + LuciHttp.write_json(result) + if code == 0 then + LuciHttp.close() + if wifirestart then + XQFunction.forkRestartWifi() + end + end +end + +function getLanInfo() + local XQLanWanUtil = require("xiaoqiang.util.XQLanWanUtil") + local lan = XQLanWanUtil.getLanWanInfo("lan") + local linkList = XQLanWanUtil.getLanLinkList() + local result = {} + result["code"] = 0 + result["info"] = lan + result["linkList"] = linkList + LuciHttp.write_json(result) +end + +function getWanInfo() + local XQLanWanUtil = require("xiaoqiang.util.XQLanWanUtil") + local wan = XQLanWanUtil.getLanWanInfo("wan") + local result = {} + result["code"] = 0 + result["info"] = wan + LuciHttp.write_json(result) +end + +function getWanStatistics() + local XQDeviceUtil = require("xiaoqiang.util.XQDeviceUtil") + local wan = XQDeviceUtil.getWanLanNetworkStatistics("wan") + local result = {} + result["code"] = 0 + result["statistics"] = wan + LuciHttp.write_json(result) +end + +function getDevsStatistics() + local XQDeviceUtil = require("xiaoqiang.util.XQDeviceUtil") + local dev = XQDeviceUtil.getDevNetStatisticsList() + local result = {} + result["code"] = 0 + result["statistics"] = dev + LuciHttp.write_json(result) +end + +function getDevStatistics() + local XQDeviceUtil = require("xiaoqiang.util.XQDeviceUtil") + local XQFunction = require("xiaoqiang.common.XQFunction") + local result = {} + local mac = LuciHttp.formvalue("mac") + local dict = XQDeviceUtil.getDevNetStatisticsDict() + local statistics = dict[XQFunction.macFormat(mac)] + result["code"] = 0 + result["statistics"] = statistics + LuciHttp.write_json(result) +end + +function getAutoWanType() + local XQLanWanUtil = require("xiaoqiang.util.XQLanWanUtil") + local XQPreference = require("xiaoqiang.XQPreference") + local XQConfigs = require("xiaoqiang.common.XQConfigs") + local result = {} + local code = 0 + local wanType = XQLanWanUtil.getAutoWanType() + if wanType == false then + code = 1524 + else + result["wanType"] = wanType + result["pppoeName"] = XQPreference.get(XQConfigs.PREF_PPPOE_NAME, "") + result["pppoePassword"] = XQPreference.get(XQConfigs.PREF_PPPOE_PASSWORD, "") + end + if code ~= 0 then + result["msg"] = XQErrorUtil.getErrorMessage(code) + end + result["code"] = code + LuciHttp.write_json(result) +end + +function getLanDhcp() + local XQLanWanUtil = require("xiaoqiang.util.XQLanWanUtil") + local result = {} + local lanDhcp = XQLanWanUtil.getLanDHCPService() + result["code"] = 0 + result["info"] = lanDhcp + LuciHttp.write_json(result) +end + +function getChannels() + local XQWifiUtil = require("xiaoqiang.util.XQWifiUtil") + local wifiIndex = tonumber(LuciHttp.formvalue("wifiIndex")) + local result = {} + result["code"] = 0 + result["list"] = XQWifiUtil.getDefaultWifiChannels(wifiIndex) + LuciHttp.write_json(result) +end + +function wanDown() + luci.sys.call("env -i /sbin/ifdown wan") + local result = {code=0} + LuciHttp.write_json(result) +end + +function wanUp() + luci.sys.call("env -i /sbin/ifup wan") + local result = {code=0} + LuciHttp.write_json(result) +end + +function setLanIp() + local XQLanWanUtil = require("xiaoqiang.util.XQLanWanUtil") + local XQFunction = require("xiaoqiang.common.XQFunction") + local LuciDatatypes = require("luci.cbi.datatypes") + local LuciIp = require("luci.ip") + local result = {} + local code = 0 + local ip = LuciHttp.formvalue("ip") + local mask = "255.255.255.0" + local wanIp = XQLanWanUtil.getLanWanIp("wan")[1] + if not LuciDatatypes.ipaddr(ip) then + code = 1525 + else + if wanIp then + local lanIpNl = LuciIp.iptonl(ip) + local lanMaskNl = LuciIp.iptonl(mask) + local wanIpNl = LuciIp.iptonl(wanIp.ip) + local wanMaskNl = LuciIp.iptonl(wanIp.mask) + if bit.band(lanIpNl,lanMaskNl) == bit.band(wanIpNl,lanMaskNl) or bit.band(lanIpNl,wanMaskNl) == bit.band(wanIpNl,wanMaskNl) then + code = 1526 + else + code = XQLanWanUtil.checkLanIp(ip) + end + end + end + if code == 0 then + XQLanWanUtil.setLanIp(ip,mask) + else + result["msg"] = XQErrorUtil.getErrorMessage(code) + end + result["code"] = code + LuciHttp.write_json(result) + if code == 0 then + LuciHttp.close() + XQFunction.forkReboot() + end +end + +function setWan() + local XQLog = require("xiaoqiang.XQLog") + local XQLanWanUtil = require("xiaoqiang.util.XQLanWanUtil") + local XQFunction = require("xiaoqiang.common.XQFunction") + local code = 0 + local result = {} + local client = LuciHttp.formvalue("client") + local wanType = LuciHttp.formvalue("wanType") + local pppoeName = LuciHttp.formvalue("pppoeName") + local pppoePwd = LuciHttp.formvalue("pppoePwd") + local staticIp = LuciHttp.formvalue("staticIp") + local staticMask = LuciHttp.formvalue("staticMask") + local staticGateway = LuciHttp.formvalue("staticGateway") + local dns1 = LuciHttp.formvalue("dns1") + local dns2 = LuciHttp.formvalue("dns2") + local special = LuciHttp.formvalue("special") + local peerDns = LuciHttp.formvalue("peerDns") + local mtu = tonumber(LuciHttp.formvalue("mtu")) + local service = LuciHttp.formvalue("service") + if XQFunction.isStrNil(wanType) + and XQFunction.isStrNil(pppoeName) + and XQFunction.isStrNil(pppoePwd) + and XQFunction.isStrNil(staticIp) + and XQFunction.isStrNil(staticMask) + and XQFunction.isStrNil(staticGateway) + and XQFunction.isStrNil(dns1) + and XQFunction.isStrNil(dns2) + and XQFunction.isStrNil(special) + and XQFunction.isStrNil(peerDns) then + code = 1502 + else + if wanType == "pppoe" then + if client == "web" then + XQLog.check(0, XQLog.KEY_VALUE_NETWORK_PPPOE, 1) + end + if XQFunction.isStrNil(pppoeName) or XQFunction.isStrNil(pppoePwd) then + code = 1528 + else + if mtu and not XQLanWanUtil.checkMTU(mtu) then + code = 1590 + else + if not XQLanWanUtil.setWanPPPoE(pppoeName, pppoePwd, dns1, dns2, peerDns, mtu, service) then + code = 1529 + end + end + end + elseif wanType == "dhcp" then + if client == "web" then + XQLog.check(0, XQLog.KEY_VALUE_NETWORK_DHCP, 1) + end + if not XQLanWanUtil.setWanStaticOrDHCP(wanType, nil, nil, nil, dns1, dns2, peerDns, mtu) then + code = 1529 + end + elseif wanType == "static" then + if client == "web" then + XQLog.check(0, XQLog.KEY_VALUE_NETWORK_STATIC, 1) + end + local LuciDatatypes = require("luci.cbi.datatypes") + local LuciIp = require("luci.ip") + if not LuciDatatypes.ipaddr(staticIp) then + code = 1530 + elseif not XQFunction.checkMask(staticMask) then + code = 1531 + elseif not LuciDatatypes.ipaddr(staticGateway) then + code = 1532 + else + local lanIp = XQLanWanUtil.getLanWanIp("lan")[1] + local lanIpNl = LuciIp.iptonl(lanIp.ip) + local lanMaskNl = LuciIp.iptonl(lanIp.mask) + local wanIpNl = LuciIp.iptonl(staticIp) + local wanMaskNl = LuciIp.iptonl(staticMask) + if bit.band(lanIpNl,lanMaskNl) == bit.band(wanIpNl,lanMaskNl) or bit.band(lanIpNl,wanMaskNl) == bit.band(wanIpNl,wanMaskNl) then + code = 1526 + else + code = XQLanWanUtil.checkWanIp(staticIp) + if code == 0 then + if not XQLanWanUtil.setWanStaticOrDHCP(wanType, staticIp, staticMask, staticGateway, dns1, dns2, peerDns, mtu) then + code = 1529 + end + end + end + end + else + -- unknown type + end + end + result["code"] = code + if code ~= 0 then + result["msg"] = XQErrorUtil.getErrorMessage(code) + end + LuciHttp.write_json(result) +end + +function setLanDhcp() + local XQLanWanUtil = require("xiaoqiang.util.XQLanWanUtil") + local LuciDatatypes = require("luci.cbi.datatypes") + local XQFunction = require("xiaoqiang.common.XQFunction") + local code = 0 + local result = {} + local startReq = tonumber(LuciHttp.formvalue("start")) + local endReq = tonumber(LuciHttp.formvalue("end")) + local leasetime = LuciHttp.formvalue("leasetime") + local ignore = LuciHttp.formvalue("ignore") + local num,unit = leasetime:match("^(%d+)(%S+)") + num = tonumber(num) + if ignore == "1" then + XQLanWanUtil.setLanDHCPService(nil,nil,nil,ignore) + else + if not LuciDatatypes.uinteger(startReq) + or not LuciDatatypes.integer(endReq) + or num == nil + or unit ~= "h" and unit ~= "m" then + code = 1537 + else + if startReq > endReq then + code = 1534 + elseif startReq <= 1 or endReq > 254 or endReq <= 1 or endReq >254 then + code = 1535 + elseif (unit == "h" and (num < 1 or num > 48)) or (unit == "m" and (num < 2 or num > 2880)) then + code = 1536 + else + XQLanWanUtil.setLanDHCPService(startReq,endReq,leasetime,ignore) + end + end + end + result["code"] = code + if code ~= 0 then + result["msg"] = XQErrorUtil.getErrorMessage(code) + end + LuciHttp.write_json(result) +end + +function setWanMac() + local XQLog = require("xiaoqiang.XQLog") + local XQLanWanUtil = require("xiaoqiang.util.XQLanWanUtil") + local code = 0 + local result = {} + local mac = LuciHttp.formvalue("mac") + local succeed = XQLanWanUtil.setWanMac(mac) + XQLog.check(0, XQLog.KEY_FUNC_MACCLONE, 1) + if not succeed then + code = 1537 + result["msg"] = XQErrorUtil.getErrorMessage(code) + end + result["code"] = code + LuciHttp.write_json(result) +end + +function getWifiMacfilterInfo() + local LuciUtil = require("luci.util") + local XQWifiUtil = require("xiaoqiang.util.XQWifiUtil") + local XQDeviceUtil = require("xiaoqiang.util.XQDeviceUtil") + local model = tonumber(LuciHttp.formvalue("model")) + local code = 0 + local result = {} + local macfilterInfo = XQWifiUtil.getWiFiMacfilterInfo(model) + local wifiList = XQDeviceUtil.getConDevices(false) + result["enable"] = macfilterInfo.enable + result["model"] = macfilterInfo.model + if macfilterInfo.maclist then + for _, device in ipairs(wifiList) do + if LuciUtil.contains(macfilterInfo.maclist, device.mac) then + device.added = 1 + else + device.added = 0 + end + end + end + result["code"] = 0 + result["list"] = wifiList + result["macfilter"] = macfilterInfo.maclist + LuciHttp.write_json(result) +end + +function setWifiMacfilter() + local XQLog = require("xiaoqiang.XQLog") + local XQFunction = require("xiaoqiang.common.XQFunction") + local XQWifiUtil = require("xiaoqiang.util.XQWifiUtil") + local enable = tonumber(LuciHttp.formvalue("enable")) == 1 and true or false + local model = tonumber(LuciHttp.formvalue("model")) + XQLog.check(0, XQLog.KEY_FUNC_WIRELESS_ACCESS, enable and 0 or 1) + if model and model == 0 then + XQLog.check(0, XQLog.KEY_FUNC_WIRELESS_BLACK, 1) + else + XQLog.check(0, XQLog.KEY_FUNC_WIRELESS_WHITE, 1) + end + XQWifiUtil.setWiFiMacfilterModel(enable, model) + local result = {["code"] = 0} + XQFunction.forkRestartWifi() + LuciHttp.write_json(result) +end + +function editDevice() + local XQWifiUtil = require("xiaoqiang.util.XQWifiUtil") + local code = 0 + local result = {} + local mac = LuciHttp.formvalue("mac") + local model = tonumber(LuciHttp.formvalue("model")) + local option = tonumber(LuciHttp.formvalue("option")) + local success = XQWifiUtil.editWiFiMacfilterList(model, mac, option) + if success and success == 1 then + code = 1591 + end + result["code"] = code + if code ~= 0 then + result["msg"] = XQErrorUtil.getErrorMessage(code) + end + LuciHttp.write_json(result) +end + +function macBind() + local XQLanWanUtil = require("xiaoqiang.util.XQLanWanUtil") + local code = 0 + local result = {} + local ip = LuciHttp.formvalue("ip") + local mac = LuciHttp.formvalue("mac") + local bind = XQLanWanUtil.addBind(mac, ip) + if bind == 1 then + code = 1593 + elseif bind == 2 then + code = 1592 + end + result["code"] = code + if code ~= 0 then + result["msg"] = XQErrorUtil.getErrorMessage(code) + end + LuciHttp.write_json(result) +end + +function macUnbind() + local XQLanWanUtil = require("xiaoqiang.util.XQLanWanUtil") + local code = 0 + local result = {} + local mac = LuciHttp.formvalue("mac") + local unbind = XQLanWanUtil.removeBind(mac) + if not unbind then + code = 1594 + end + result["code"] = code + if code ~= 0 then + result["msg"] = XQErrorUtil.getErrorMessage(code) + end + LuciHttp.write_json(result) +end + +function saveBind() + local XQFunction = require("xiaoqiang.common.XQFunction") + local XQLanWanUtil = require("xiaoqiang.util.XQLanWanUtil") + local result = { + ["code"] = 0 + } + XQLanWanUtil.saveBindInfo() + XQFunction.forkRestartDnsmasq() + LuciHttp.write_json(result) +end + +function unbindAll() + local XQFunction = require("xiaoqiang.common.XQFunction") + local XQLanWanUtil = require("xiaoqiang.util.XQLanWanUtil") + local result = { + ["code"] = 0 + } + XQLanWanUtil.unbindAll() + XQFunction.forkRestartDnsmasq() + LuciHttp.write_json(result) +end + +function getMacBindInfo() + local XQDeviceUtil = require("xiaoqiang.util.XQDeviceUtil") + local XQLanWanUtil = require("xiaoqiang.util.XQLanWanUtil") + local result = { + ["code"] = 0 + } + local blist = {} + local bindInfo = XQLanWanUtil.macBindInfo() + local deviceList = XQDeviceUtil.getConDevices(true) or {} + for _, device in ipairs(deviceList) do + local bind = bindInfo[string.lower(device.mac)] + if bind then + device["tag"] = bind.tag + else + device["tag"] = 0 + end + end + for _, host in pairs(bindInfo) do + table.insert(blist, { + ["mac"] = string.upper(host.mac), + ["ip"] = host.ip, + ["tag"] = host.tag + }) + end + result["list"] = blist + result["devicelist"] = deviceList + LuciHttp.write_json(result) +end + +function pppoeStatus() + local XQLanWanUtil = require("xiaoqiang.util.XQLanWanUtil") + local code = 0 + local result = {} + local status = XQLanWanUtil.getPPPoEStatus() + if status then + result = status + if result.errtype == 1 then + code = 1603 + elseif result.errtype == 2 then + code = 1604 + elseif result.errtype == 3 then + code = 1605 + end + else + code = 1602 + end + if code ~= 0 then + if code ~= 1602 then + result["msg"] = string.format("%s(%s)",XQErrorUtil.getErrorMessage(code), tostring(result.errcode)) + else + result["msg"] = XQErrorUtil.getErrorMessage(code) + end + end + result["code"] = code + LuciHttp.write_json(result) +end + +function pppoeStop() + local XQLanWanUtil = require("xiaoqiang.util.XQLanWanUtil") + local result = { + ["code"] = 0 + } + XQLanWanUtil.pppoeStop() + LuciHttp.write_json(result) +end + +function pppoeStart() + local XQLanWanUtil = require("xiaoqiang.util.XQLanWanUtil") + local result = { + ["code"] = 0 + } + XQLanWanUtil.pppoeStart() + LuciHttp.write_json(result) +end + +function getQosInfo() + local XQQoSUtil = require("xiaoqiang.util.XQQoSUtil") + local result = { + ["code"] = 0 + } + local status = XQQoSUtil.qosStatus() + result["status"] = status + if status.on == 1 then + result["band"] = XQQoSUtil.qosBand() + result["list"] = XQQoSUtil.qosList() + end + LuciHttp.write_json(result) +end + +function qosSwitch() + local XQLog = require("xiaoqiang.XQLog") + local XQQoSUtil = require("xiaoqiang.util.XQQoSUtil") + local result = { + ["code"] = 0 + } + local on = tonumber(LuciHttp.formvalue("on")) == 1 and true or false + XQLog.check(0, XQLog.KEY_FUNC_QOS, on and 0 or 1) + local switch = XQQoSUtil.qosSwitch(on) + if not switch then + result.code = 1606 + end + if result.code ~= 0 then + result["msg"] = XQErrorUtil.getErrorMessage(result.code) + end + LuciHttp.write_json(result) +end + +function qosMode() + local XQQoSUtil = require("xiaoqiang.util.XQQoSUtil") + local result = { + ["code"] = 0 + } + local auto = tonumber(LuciHttp.formvalue("mode")) == 0 and true or false + local status = XQQoSUtil.qosStatus() + local switch + if status and status.on == 1 then + switch = XQQoSUtil.qosModeSwitch(auto) + else + result.code = 1607 + end + if not switch and result.code == 0 then + result.code = 1606 + end + if result.code ~= 0 then + result["msg"] = XQErrorUtil.getErrorMessage(result.code) + end + LuciHttp.write_json(result) +end + +-- level : 1/2/3 low/middle/high +-- upload : 1 ~ 100 +-- download : 1 ~ 100 +function qosLimit() + local XQQoSUtil = require("xiaoqiang.util.XQQoSUtil") + local result = { + ["code"] = 0 + } + local mac = LuciHttp.formvalue("mac") + local upload = tonumber(LuciHttp.formvalue("upload")) + local download = tonumber(LuciHttp.formvalue("download")) + local level = tonumber(LuciHttp.formvalue("level")) + local limit + local status = XQQoSUtil.qosStatus() + if status and status.on == 1 then + if mac and upload and download and level then + limit = XQQoSUtil.qosOnLimit(mac, upload/100, download/100, level, level) + else + result.code = 1523 + end + else + result.code = 1607 + end + if not limit and result.code == 0 then + result.code = 1606 + end + if result.code ~= 0 then + result["msg"] = XQErrorUtil.getErrorMessage(result.code) + end + LuciHttp.write_json(result) +end + +function qosOffLimit() + local XQQoSUtil = require("xiaoqiang.util.XQQoSUtil") + local result = { + ["code"] = 0 + } + local mac = LuciHttp.formvalue("mac") + local status = XQQoSUtil.qosStatus() + local offlimit + if status and status.on == 1 then + offlimit = XQQoSUtil.qosOffLimit(mac) + else + result.code = 1607 + end + if not offlimit and result.code == 0 then + result.code = 1606 + end + if result.code ~= 0 then + result["msg"] = XQErrorUtil.getErrorMessage(result.code) + end + LuciHttp.write_json(result) +end + +-- upload/download M bits/s +function setBand() + local XQQoSUtil = require("xiaoqiang.util.XQQoSUtil") + local result = { + ["code"] = 0 + } + local upload = tonumber(LuciHttp.formvalue("upload")) + local download = tonumber(LuciHttp.formvalue("download")) + local band + local status = XQQoSUtil.qosStatus() + if upload and download then + if status and status.on == 1 then + band = XQQoSUtil.setQosBand(upload, download) + else + result.code = 1607 + end + else + result.code = 1523 + end + if not band and result.code == 0 then + result.code = 1606 + end + if result.code ~= 0 then + result["msg"] = XQErrorUtil.getErrorMessage(result.code) + end + LuciHttp.write_json(result) +end + +function portForward() + local XQPortForward = require("xiaoqiang.module.XQPortForward") + local result = { + ["code"] = 0 + } + local ftype = tonumber(LuciHttp.formvalue("ftype")) or 0 + result["status"] = XQPortForward.portForwardInfo().status + result["list"] = XQPortForward.portForwards(ftype) + LuciHttp.write_json(result) +end + +function addRedirect() + local XQLog = require("xiaoqiang.XQLog") + local XQPortForward = require("xiaoqiang.module.XQPortForward") + local result = { + ["code"] = 0 + } + local ip = LuciHttp.formvalue("ip") + local name = LuciHttp.formvalue("name") + local proto = tonumber(LuciHttp.formvalue("proto")) + local sport = tonumber(LuciHttp.formvalue("sport")) + local dport = tonumber(LuciHttp.formvalue("dport")) + local add = XQPortForward.setPortForward(name, ip, sport, dport, proto) + XQLog.check(0, XQLog.KEY_FUNC_PORTFADD, 1) + if add == 1 then + result.code = 1537 + elseif add == 2 then + result.code = 1608 + elseif add == 3 then + result.code = 1609 + end + if result.code ~= 0 then + result["msg"] = XQErrorUtil.getErrorMessage(result.code) + end + LuciHttp.write_json(result) +end + +function addRangeRedirect() + local XQLog = require("xiaoqiang.XQLog") + local XQPortForward = require("xiaoqiang.module.XQPortForward") + local result = { + ["code"] = 0 + } + local ip = LuciHttp.formvalue("ip") + local name = LuciHttp.formvalue("name") + local proto = tonumber(LuciHttp.formvalue("proto")) + local fport = tonumber(LuciHttp.formvalue("fport")) + local tport = tonumber(LuciHttp.formvalue("tport")) + local add = XQPortForward.setRangePortForward(name, ip, fport, tport, proto) + XQLog.check(0, XQLog.KEY_FUNC_RANGEFADD, 1) + if add == 1 then + result.code = 1537 + elseif add == 2 then + result.code = 1608 + elseif add == 3 then + result.code = 1609 + end + if result.code ~= 0 then + result["msg"] = XQErrorUtil.getErrorMessage(result.code) + end + LuciHttp.write_json(result) +end + +function deleteRedirect() + local XQPortForward = require("xiaoqiang.module.XQPortForward") + local result = { + ["code"] = 0 + } + local port = tonumber(LuciHttp.formvalue("port")) or 0 + if port == 0 then + XQPortForward.deleteAllPortForward() + else + XQPortForward.deletePortForward(port) + end + LuciHttp.write_json(result) +end + +function redirectApply() + local XQLog = require("xiaoqiang.XQLog") + local XQPortForward = require("xiaoqiang.module.XQPortForward") + local result = { + ["code"] = 0 + } + XQLog.check(0, XQLog.KEY_FUNC_PORTENABLE, 1) + XQPortForward.restart() + LuciHttp.write_json(result) +end + +function getDMZInfo() + local XQDMZModule = require("xiaoqiang.module.XQDMZModule") + local result = { + ["code"] = 0 + } + local info = XQDMZModule.getDMZInfo() + result["status"] = info.status + result["ip"] = info.ip + result["lanip"] = info.lanip + LuciHttp.write_json(result) +end + +function setDMZ() + local XQLog = require("xiaoqiang.XQLog") + local XQDMZModule = require("xiaoqiang.module.XQDMZModule") + local result = { + ["code"] = 0 + } + local ip = LuciHttp.formvalue("ip") + local mac = LuciHttp.formvalue("mac") + local mode = tonumber(LuciHttp.formvalue("mode")) or 0 + local set = XQDMZModule.setDMZ(mode, ip, mac) + if set == 1 then + result.code = 1593 + elseif set == 2 then + result.code = 1592 + elseif set == 3 then + result.code = 1611 + elseif set == 4 then + result.code = 1610 + end + XQLog.check(0, XQLog.KEY_FUNC_DMZ, 0) + if result.code ~= 0 then + result["msg"] = XQErrorUtil.getErrorMessage(result.code) + else + XQDMZModule.dmzReload(mode) + end + LuciHttp.write_json(result) +end + +function closeDMZ() + local XQLog = require("xiaoqiang.XQLog") + local XQDMZModule = require("xiaoqiang.module.XQDMZModule") + local result = { + ["code"] = 0 + } + local mode = tonumber(LuciHttp.formvalue("mode")) or 0 + XQLog.check(0, XQLog.KEY_FUNC_DMZ, 1) + XQDMZModule.unsetDMZ(mode) + LuciHttp.write_json(result) +end + +function reloadDMZ() + local XQDMZModule = require("xiaoqiang.module.XQDMZModule") + local result = { + ["code"] = 0 + } + local mode = tonumber(LuciHttp.formvalue("mode")) or 0 + XQDMZModule.dmzReload(mode) + LuciHttp.write_json(result) +end + +function ddnsStatus() + local XQDDNS = require("xiaoqiang.module.XQDDNS") + local result = { + ["code"] = 0 + } + local ddns = XQDDNS.ddnsInfo() + result["on"] = ddns.on + result["list"] = ddns.list + LuciHttp.write_json(result) +end + +function ddnsSwitch() + local XQDDNS = require("xiaoqiang.module.XQDDNS") + local result = { + ["code"] = 0 + } + local on = tonumber(LuciHttp.formvalue("on")) == 1 and true or false + XQDDNS.ddnsSwitch(on) + LuciHttp.write_json(result) +end + +function addServer() + local XQDDNS = require("xiaoqiang.module.XQDDNS") + local result = { + ["code"] = 0 + } + local id = tonumber(LuciHttp.formvalue("id")) + local enable = tonumber(LuciHttp.formvalue("enable")) == 1 and 1 or 0 + local domain = LuciHttp.formvalue("domain") or "" + local username = LuciHttp.formvalue("username") or "" + local password = LuciHttp.formvalue("password") or "" + local checkinterval = tonumber(LuciHttp.formvalue("checkinterval")) + local forceinterval = tonumber(LuciHttp.formvalue("forceinterval")) + if not id or not checkinterval or not forceinterval then + result.code = 1612 + elseif checkinterval <= 0 or forceinterval <= 0 then + result.code = 1523 + else + local add = XQDDNS.setDdns(id, enable, username, password, checkinterval, forceinterval, domain) + if not add then + result.code = 1606 + end + end + if result.code ~= 0 then + result["msg"] = XQErrorUtil.getErrorMessage(result.code) + end + LuciHttp.write_json(result) +end + +function deleteServer() + local XQDDNS = require("xiaoqiang.module.XQDDNS") + local result = { + ["code"] = 0 + } + local id = tonumber(LuciHttp.formvalue("id")) + if not id then + result.code = 1612 + else + local delete = XQDDNS.deleteDdns(id) + if not delete then + result.code = 1606 + end + end + if result.code ~= 0 then + result["msg"] = XQErrorUtil.getErrorMessage(result.code) + end + LuciHttp.write_json(result) +end + +function serverSwitch() + local XQDDNS = require("xiaoqiang.module.XQDDNS") + local result = { + ["code"] = 0 + } + local id = tonumber(LuciHttp.formvalue("id")) + local on = tonumber(LuciHttp.formvalue("on")) == 1 and true or false + if not id then + result.code = 1612 + else + local switch = XQDDNS.ddnsServerSwitch(id, on) + if not switch then + result.code = 1606 + end + end + if result.code ~= 0 then + result["msg"] = XQErrorUtil.getErrorMessage(result.code) + end + LuciHttp.write_json(result) +end + +function ddnsReload() + local XQDDNS = require("xiaoqiang.module.XQDDNS") + local result = { + ["code"] = 0 + } + if not XQDDNS.reload() then + result.code = 1606 + end + if result.code ~= 0 then + result["msg"] = XQErrorUtil.getErrorMessage(result.code) + end + LuciHttp.write_json(result) +end + +function getServer() + local XQDDNS = require("xiaoqiang.module.XQDDNS") + local result = {} + local id = tonumber(LuciHttp.formvalue("id")) + local get = XQDDNS.getDdns(id) + if get then + result = get + result["code"] = 0 + else + result["code"] = 1614 + end + if result.code ~= 0 then + result["msg"] = XQErrorUtil.getErrorMessage(result.code) + end + LuciHttp.write_json(result) +end + +function ddnsEdit() + local XQDDNS = require("xiaoqiang.module.XQDDNS") + local result = { + ["code"] = 0 + } + local id = tonumber(LuciHttp.formvalue("id")) + local enable = tonumber(LuciHttp.formvalue("enable")) == 1 and 1 or 0 + local domain = LuciHttp.formvalue("domain") + local username = LuciHttp.formvalue("username") + local password = LuciHttp.formvalue("password") + local checkinterval = tonumber(LuciHttp.formvalue("checkinterval")) + local forceinterval = tonumber(LuciHttp.formvalue("forceinterval")) + local edit = XQDDNS.editDdns(id, enable, username, password, checkinterval, forceinterval, domain) + if not edit then + result.code = 1606 + end + if result.code ~= 0 then + result["msg"] = XQErrorUtil.getErrorMessage(result.code) + end + LuciHttp.write_json(result) +end diff --git a/Mi_Lua/luci/controller/api/xqpassport.lua b/Mi_Lua/luci/controller/api/xqpassport.lua new file mode 100644 index 0000000..951475a --- /dev/null +++ b/Mi_Lua/luci/controller/api/xqpassport.lua @@ -0,0 +1,316 @@ +module("luci.controller.api.xqpassport", package.seeall) + +function index() + local page = node("api","xqpassport") + page.target = firstchild() + page.title = ("") + page.order = 400 + page.sysauth = "admin" + page.sysauth_authenticator = "jsonauth" + page.index = true + entry({"api", "xqpassport"}, firstchild(), (""), 400) + entry({"api", "xqpassport", "login"}, call("passportLogin"), (""), 401, 0x01) + entry({"api", "xqpassport", "userInfo"}, call("getUserInfo"), (""), 402) + entry({"api", "xqpassport", "rigister"}, call("routerRegister"), (""), 405, 0x01) + entry({"api", "xqpassport", "binded"}, call("getBindInfo"), (""), 406, 0x01) + entry({"api", "xqpassport", "plugin_list"}, call("pluginList"), (""), 407) + entry({"api", "xqpassport", "plugin_enable"}, call("pluginEnable"), (""), 408) + entry({"api", "xqpassport", "plugin_disable"}, call("pluginDisable"), (""), 409) + entry({"api", "xqpassport", "plugin_detail"}, call("pluginDetail"), (""), 410) + entry({"api", "xqpassport", "unbound"}, call("unboundRouter"), (""), 411) +end + +local LuciHttp = require("luci.http") +local XQErrorUtil = require("xiaoqiang.util.XQErrorUtil") + +function getBindInfo() + local XQNetUtil = require("xiaoqiang.util.XQNetUtil") + local XQSysUtil = require("xiaoqiang.util.XQSysUtil") + local uuid = LuciHttp.formvalue("uuid") or "" + local force = tonumber(LuciHttp.formvalue("force") or "0") + local result = {} + local code = 0 + local bindUUID = XQSysUtil.getPassportBindInfo() + if bindUUID then + result["bind"] = 1 + local info = XQSysUtil.getBindUserInfo() + if info == nil or force ~= 0 then + info = XQNetUtil.getUserInfo(uuid) + end + if info then + if info.miliaoNick and info.miliaoNick ~= "" then + info.aliasNick = info.miliaoNick + end + result["info"] = info + else + info = {} + info["aliasNick"] = bindUUID + info["miliaoIcon"] = "" + info["miliaoIconOrig"] = "" + info["miliaoNick"] = "" + info["userId"] = bindUUID + result["info"] = info + end + else + result["bind"] = 0 + end + result["routerName"] = XQSysUtil.getRouterName() + + if code ~= 0 then + result["msg"] = XQErrorUtil.getErrorMessage(code) + end + result["code"] = code + LuciHttp.write_json(result) +end + +function unboundRouter() + local XQNetUtil = require("xiaoqiang.util.XQNetUtil") + local XQDBUtil = require("xiaoqiang.util.XQDBUtil") + local XQSysUtil = require("xiaoqiang.util.XQSysUtil") + local result = {} + local code = 0 + local uuid = LuciHttp.formvalue("uuid") + local password = LuciHttp.formvalue("password") + if uuid == nil or uuid == "" then + uuid = XQSysUtil.getBindUUID() + end + if password ~= nil then + local login = XQNetUtil.xiaomiLogin(uuid,password) + if login and login.code == 0 then + if XQSysUtil.getPassportBindInfo() then + local unbound = XQNetUtil.dismissAccount(nil,uuid) + if unbound and (tonumber(unbound.code) == 0 or tonumber(unbound.code) == 3001 or tonumber(unbound.code) == 3002) then + XQSysUtil.setPassportBound(false,uuid) + else + code = 1550 + end + end + else + code = 1556 + end + else + code = 1557 + end + if code ~= 0 then + result["msg"] = XQErrorUtil.getErrorMessage(code) + else + LuciHttp.header("Set-Cookie", "psp=admin|||2|||0;path=/;") + end + result["code"] = code + LuciHttp.write_json(result) +end + +function passportLogin() + local XQNetUtil = require("xiaoqiang.util.XQNetUtil") + local XQDBUtil = require("xiaoqiang.util.XQDBUtil") + local XQSysUtil = require("xiaoqiang.util.XQSysUtil") + local result = {} + local code = 0 + local uuid = LuciHttp.formvalue("uuid") + local password = LuciHttp.formvalue("password") + local encrypt = LuciHttp.formvalue("encrypt") + local login = XQNetUtil.xiaomiLogin(uuid,password) + if login and login.code == 0 then + local bindInfo = XQSysUtil.getPassportBindInfo() + if bindInfo then + if login.uuid == bindInfo then + local adminList = XQNetUtil.getAdminList() + if adminList and type(adminList) == "table" then + if tonumber(adminList.code) == 0 then + code = 0 + LuciHttp.header("Set-Cookie", "psp=" .. login.uuid .. "|||" .. 1 .. "|||" .. login.token .. ";path=/;") + elseif tonumber(adminList.code) == 401 then + code = 1551 + else + code = 1549 + XQSysUtil.setPassportBound(false,login.uuid) + LuciHttp.header("Set-Cookie", "psp=admin|||2|||0;path=/;") + end + else + code = 1551 + if adminList and adminList.msg then + result["errorDetail"] = adminList.msg + end + end + else + code = 1548 + end + else + XQSysUtil.setBindUUID(login.uuid) + end + result["token"] = login.token + result["uuid"] = login.uuid + elseif login and login.code ~= 0 then + if login.code == 1 then + code = 1564 + elseif login.code == 2 then + code = 1565 + else + code = 1566 + end + else + code = 1538 + end + if code ~= 0 then + local XQFunction = require("xiaoqiang.common.XQFunction") + XQFunction.forkExec("/usr/sbin/ntpsetclock 99999 log >/dev/null 2>&1") + result["msg"] = XQErrorUtil.getErrorMessage(code) + end + result["code"] = code + LuciHttp.write_json(result) +end + +function routerAdminList() + local XQNetUtil = require("xiaoqiang.util.XQNetUtil") + local XQSysUtil = require("xiaoqiang.util.XQSysUtil") + local result = {} + local code = 0 + local uuid = LuciHttp.formvalue("uuid") or "" + if not XQSysUtil.getPassportBindInfo() then + code = 1542 + else + local admin = XQNetUtil.getAdminList(uuid) + if admin and tonumber(admin.code) == 0 then + result["list"] = admin.adminList + elseif admin and tonumber(admin.code) == 401 then + code = 1581 + else + code = 1543 + end + end + if code ~= 0 then + result["msg"] = XQErrorUtil.getErrorMessage(code) + end + result["code"] = code + LuciHttp.write_json(result) +end + +function routerRegister() + local XQSysUtil = require("xiaoqiang.util.XQSysUtil") + local XQNetUtil = require("xiaoqiang.util.XQNetUtil") + local XQDBUtil = require("xiaoqiang.util.XQDBUtil") + local result = {} + local code = 0 + local uuid = LuciHttp.formvalue("uuid") + local register = XQNetUtil.routerRegister(uuid) + local passport = XQNetUtil.getPassport(uuid) + if register and tonumber(register.code) == 0 then + result["deviceID"] = register.id + XQSysUtil.setPassportBound(true,passport.uuid) + else + XQSysUtil.setPassportBound(false,nil) + code = 1541 + end + if code ~= 0 then + local XQFunction = require("xiaoqiang.common.XQFunction") + XQFunction.forkExec("/usr/sbin/ntpsetclock 99999 log >/dev/null 2>&1") + result["msg"] = XQErrorUtil.getErrorMessage(code) + else + LuciHttp.header("Set-Cookie", "psp=" .. uuid .. "|||" .. 1 .. "|||" .. passport.token .. ";path=/;") + end + result["code"] = code + LuciHttp.write_json(result) +end + +function getUserInfo() + local XQNetUtil = require("xiaoqiang.util.XQNetUtil") + local result = {} + local code = 0 + local uuid = LuciHttp.formvalue("uuid") or "" + local info = XQNetUtil.getUserInfo(uuid) + if info then + result["userInfo"] = info + else + code = 1539 + end + if code ~= 0 then + result["msg"] = XQErrorUtil.getErrorMessage(code) + end + result["code"] = code + LuciHttp.write_json(result) +end + +function pluginList() + local XQNetUtil = require("xiaoqiang.util.XQNetUtil") + local result = {} + local uuid = LuciHttp.formvalue("uuid") or "" + local pList = XQNetUtil.pluginList(uuid) + if pList and tonumber(pList.code) == 0 then + result["code"] = 0 + result["list"] = pList + elseif pList and tonumber(pList.code) == 401 then + result["code"] = 1581 + elseif pList and tonumber(pList.code) == 3001 then + result["code"] = 1580 + else + result["code"] = 1544 + end + if result.code ~= 0 then + result["msg"] = XQErrorUtil.getErrorMessage(result.code) + end + LuciHttp.write_json(result) +end + +function pluginEnable() + local XQNetUtil = require("xiaoqiang.util.XQNetUtil") + local result = {} + local uuid = LuciHttp.formvalue("uuid") or "" + local pluginId = LuciHttp.formvalue("pluginId") + local enable = XQNetUtil.pluginEnable(uuid,pluginId) + if enable and tonumber(enable.code) == 0 then + result["code"] = 0 + elseif enable and tonumber(enable.code) == 401 then + result["code"] = 1581 + elseif enable and tonumber(enable.code) == 3001 then + result["code"] = 1580 + else + result["code"] = 1545 + end + if result.code ~= 0 then + result["msg"] = XQErrorUtil.getErrorMessage(result.code) + end + LuciHttp.write_json(result) +end + +function pluginDisable() + local XQNetUtil = require("xiaoqiang.util.XQNetUtil") + local result = {} + local uuid = LuciHttp.formvalue("uuid") or "" + local pluginId = LuciHttp.formvalue("pluginId") + local disable = XQNetUtil.pluginDisable(uuid,pluginId) + if disable and tonumber(disable.code) == 0 then + result["code"] = 0 + elseif disable and tonumber(disable.code) == 401 then + result["code"] = 1581 + elseif disable and tonumber(disable.code) == 3001 then + result["code"] = 1580 + else + result["code"] = 1546 + end + if result.code ~= 0 then + result["msg"] = XQErrorUtil.getErrorMessage(result.code) + end + LuciHttp.write_json(result) +end + +function pluginDetail() + local XQNetUtil = require("xiaoqiang.util.XQNetUtil") + local result = {} + local uuid = LuciHttp.formvalue("uuid") or "" + local pluginId = LuciHttp.formvalue("pluginId") + local plugin = XQNetUtil.pluginDetail(uuid,pluginId) + if plugin and tonumber(plugin.code) == 0 then + result["code"] = 0 + result["detail"] = plugin + elseif plugin and tonumber(plugin.code) == 401 then + result["code"] = 1581 + elseif plugin and tonumber(plugin.code) == 3001 then + result["code"] = 1580 + else + result["code"] = 1547 + end + if result.code ~= 0 then + result["msg"] = XQErrorUtil.getErrorMessage(result.code) + end + LuciHttp.write_json(result) +end diff --git a/Mi_Lua/luci/controller/api/xqsmarthome.lua b/Mi_Lua/luci/controller/api/xqsmarthome.lua new file mode 100644 index 0000000..3bc69db --- /dev/null +++ b/Mi_Lua/luci/controller/api/xqsmarthome.lua @@ -0,0 +1,67 @@ +module("luci.controller.api.xqsmarthome", package.seeall) + +function index() + local page = node("api","xqsmarthome") + page.target = firstchild() + page.title = ("") + page.order = 500 + page.sysauth = "admin" + page.sysauth_authenticator = "jsonauth" + page.index = true + entry({"api", "xqsmarthome"}, firstchild(), _(""), 500) + entry({"api", "xqsmarthome", "request"}, call("tunnelSmartHomeRequest"), _(""), 501) + entry({"api", "xqsmarthome", "request_smartcontroller"}, call("tunnelSmartControllerRequest"), _(""), 502) + entry({"api", "xqsmarthome", "request_miio"}, call("tunnelMiioRequest"), _(""), 503) + entry({"api", "xqsmarthome", "request_mitv"}, call("requestMitv"), _(""), 504) + entry({"api", "xqsmarthome", "request_yeelink"}, call("tunnelYeelink"), _(""), 505) + entry({"api", "xqsmarthome", "request_camera"}, call("requestCamera"), _(""), 506) +end + +local LuciHttp = require("luci.http") +local XQConfigs = require("xiaoqiang.common.XQConfigs") +local XQFunction = require("xiaoqiang.common.XQFunction") + +function tunnelSmartHomeRequest() + local XQCryptoUtil = require("xiaoqiang.util.XQCryptoUtil") + local payload = XQCryptoUtil.binaryBase64Enc(LuciHttp.formvalue("payload")) + local cmd = XQConfigs.THRIFT_TUNNEL_TO_SMARTHOME % payload + local LuciUtil = require("luci.util") + LuciHttp.write(LuciUtil.exec(cmd)) +end + +function tunnelSmartControllerRequest() + local XQCryptoUtil = require("xiaoqiang.util.XQCryptoUtil") + local payload = XQCryptoUtil.binaryBase64Enc(LuciHttp.formvalue("payload")) + local cmd = XQConfigs.THRIFT_TUNNEL_TO_SMARTHOME_CONTROLLER % payload + local LuciUtil = require("luci.util") + LuciHttp.write(LuciUtil.exec(cmd)) +end + +function tunnelMiioRequest() + local XQCryptoUtil = require("xiaoqiang.util.XQCryptoUtil") + local payload = XQCryptoUtil.binaryBase64Enc(LuciHttp.formvalue("payload")) + local cmd = XQConfigs.THRIFT_TUNNEL_TO_MIIO % payload + local LuciUtil = require("luci.util") + LuciHttp.write(LuciUtil.exec(cmd)) +end + +function tunnelYeelink() + local XQCryptoUtil = require("xiaoqiang.util.XQCryptoUtil") + local payload = XQCryptoUtil.binaryBase64Enc(LuciHttp.formvalue("payload")) + -- merge yeelink daemon into miio, so tunnel into miio + local cmd = XQConfigs.THRIFT_TUNNEL_TO_MIIO % payload + local LuciUtil = require("luci.util") + LuciHttp.write(LuciUtil.exec(cmd)) +end + +function requestMitv() + local payload = LuciHttp.formvalue("payload"); + local MitvUtil = require("xiaoqiang.util.XQMitvUtil"); + LuciHttp.write(MitvUtil.request(payload)); +end + +function requestCamera() + local payload = LuciHttp.formvalue("payload"); + local CamUtil = require("xiaoqiang.util.XQCameraUtil"); + LuciHttp.write(CamUtil.request(payload)); +end diff --git a/Mi_Lua/luci/controller/api/xqsystem.lua b/Mi_Lua/luci/controller/api/xqsystem.lua new file mode 100644 index 0000000..a613b41 --- /dev/null +++ b/Mi_Lua/luci/controller/api/xqsystem.lua @@ -0,0 +1,1598 @@ +module("luci.controller.api.xqsystem", package.seeall) + +function index() + local page = node("api","xqsystem") + page.target = firstchild() + page.title = ("") + page.order = 100 + page.sysauth = "admin" + page.sysauth_authenticator = "jsonauth" + page.index = true + entry({"api", "xqsystem"}, firstchild(), (""), 100) + entry({"api", "xqsystem", "login"}, call("actionLogin"), (""), 109, 0x08) + entry({"api", "xqsystem", "init_info"}, call("getInitInfo"), (""), 101, 0x09) + entry({"api", "xqsystem", "fac_info"}, call("getFacInfo"), (""), 101, 0x09) + entry({"api", "xqsystem", "token"}, call("getToken"), (""), 103, 0x08) + entry({"api", "xqsystem", "set_inited"}, call("setInited"), (""), 103, 0x08) + entry({"api", "xqsystem", "system_info"}, call("getSysInfo"), (""), 104, 0x01) + entry({"api", "xqsystem", "set_name_password"}, call("setPassword"), (""), 105) + entry({"api", "xqsystem", "check_rom_update"}, call("checkRomUpdate"), (""), 106) + entry({"api", "xqsystem", "lan_wan"}, call("getLanWanSta"), (""), 106) + -- for Web only + entry({"api", "xqsystem", "flash_rom"}, call("flashRom"), (""), 108) + + -- deprecated + entry({"api", "xqsystem", "router_name"}, call("getRouterName"), (""), 110) + + entry({"api", "xqsystem", "device_list"}, call("getDeviceList"), (""), 112) + entry({"api", "xqsystem", "set_device_nickname"}, call("setDeviceNickName"), (""), 113) + entry({"api", "xqsystem", "internet_connect"}, call("isInternetConnect"), (""), 114) + entry({"api", "xqsystem", "upload_rom"}, call("uploadRom"), (""), 115) + entry({"api", "xqsystem", "get_languages"}, call("getLangList"), (""), 118, 0x01) + entry({"api", "xqsystem", "get_main_language"}, call("getMainLang"), (""), 119, 0x01) + entry({"api", "xqsystem", "set_language"}, call("setLang"), (""), 120) + + entry({"api", "xqsystem", "upload_log"}, call("uploadLogFile"), (""), 124) + entry({"api", "xqsystem", "backup_config"}, call("uploadConfigFile"), (""), 125) + entry({"api", "xqsystem", "config_recovery"}, call("configRecovery"), (""), 126) + entry({"api", "xqsystem", "router_init"}, call("setRouter"), (""), 126, 0x08) + entry({"api", "xqsystem", "information"}, call("getAllInfo"), (""), 127) + entry({"api", "xqsystem", "status"}, call("getStatusInfo"), (""), 128) + entry({"api", "xqsystem", "count"}, call("getConDevCount"), (""), 129) + entry({"api", "xqsystem", "reboot"}, call("reboot"), (""), 130) + entry({"api", "xqsystem", "reset"}, call("reset"), (""), 131) + entry({"api", "xqsystem", "passport_bind_info"}, call("getPassportBindInfo"), (""), 132, 0x01) + entry({"api", "xqsystem", "set_passport_bound"}, call("setPassportBound"), (""), 133, 0x08) + entry({"api", "xqsystem", "get_sys_avg_load"}, call("getSysAvgLoad"), (""), 134) + entry({"api", "xqsystem", "set_mac_filter"}, call("setMacFilter"), (""), 135) + entry({"api", "xqsystem", "renew_token"}, call("renewToken"), (""), 136) + entry({"api", "xqsystem", "remove_passport_info"}, call("removePassportBindInfo"), (""), 137) + entry({"api", "xqsystem", "upgrade_rom"}, call("upgradeRom"), (""), 138) + entry({"api", "xqsystem", "wps"}, call("openWps"), (""), 139, 0x08) + entry({"api", "xqsystem", "wps_status"}, call("getWpsStatus"), (""), 140) + entry({"api", "xqsystem", "stop_nginx"}, call("stopNginx"), (""), 141) + entry({"api", "xqsystem", "check_router_name_pending"}, call("checkRouterNamePending"), (""), 142) + entry({"api", "xqsystem", "clear_router_name_pending"}, call("clearRouterNamePending"), (""), 143) + entry({"api", "xqsystem", "web_url"}, call("redirectUrl"), (""), 144) + entry({"api", "xqsystem", "start_nginx"}, call("startNginx"), (""), 145) + entry({"api", "xqsystem", "nginx"}, call("nginxCacheStatus"), (""), 146) + entry({"api", "xqsystem", "flash_status"}, call("flashStatus"), (""), 147, 0x01) + entry({"api", "xqsystem", "upgrade_status"}, call("upgradeStatus"), (""), 148, 0x0d) + entry({"api", "xqsystem", "create_sandbox"}, call("createSandbox"), (""), 149) + entry({"api", "xqsystem", "is_sandbox_created"}, call("isSandboxCreated"), (""), 150) + entry({"api", "xqsystem", "mount_things"}, call("mountThings"), (""), 151) + entry({"api", "xqsystem", "umount_things"}, call("umountThings"), (""), 152) + entry({"api", "xqsystem", "are_things_mounted"}, call("areThingsMounted"), (""), 153) + entry({"api", "xqsystem", "start_dropbear"}, call("startDropbear"), (""), 154) + entry({"api", "xqsystem", "stop_dropbear"}, call("stopDropbear"), (""), 155) + entry({"api", "xqsystem", "is_dropbear_started"}, call("isDropbearStarted"), (""), 156) + entry({"api", "xqsystem", "main_status_for_app"}, call("mainStatusForApp"), (""), 157) + entry({"api", "xqsystem", "mode"}, call("getMacfilterMode"), (""), 158) + entry({"api", "xqsystem", "set_mode"}, call("setMacfilterMode"), (""), 159) + entry({"api", "xqsystem", "cancel"}, call("cancelUpgrade"), (""), 160, 0x0d) + entry({"api", "xqsystem", "shutdown"}, call("shutdown"), (""), 161) + entry({"api", "xqsystem", "upnp"}, call("upnpList"), (""), 162) + entry({"api", "xqsystem", "upnp_switch"}, call("upnpSwitch"), (""), 163) + entry({"api", "xqsystem", "app_limit"}, call("appLimit"), (""), 164) + entry({"api", "xqsystem", "app_limit_switch"}, call("appLimitSwitch"), (""), 165) + entry({"api", "xqsystem", "set_app_limit"}, call("setAppLimit"), (""), 166) + entry({"api", "xqsystem", "vpn"}, call("vpnInfo"), (""), 167) + entry({"api", "xqsystem", "vpn_status"}, call("vpnStatus"), (""), 168) + entry({"api", "xqsystem", "vpn_switch"}, call("vpnSwitch"), (""), 169) + entry({"api", "xqsystem", "set_vpn"}, call("setVpn"), (""), 170) + entry({"api", "xqsystem", "device_mac"}, call("getDeviceMacaddr"), (""), 171, 0x01) + entry({"api", "xqsystem", "wps_cancel"}, call("stopWps"), (""), 172) + entry({"api", "xqsystem", "detection_ts"}, call("getDetectionTimestamp"), (""), 173) + entry({"api", "xqsystem", "wifi_log"}, call("getWifiLog"), (""), 174) + entry({"api", "xqsystem", "sys_recovery"}, call("sysRecovery"), (""), 175) + entry({"api", "xqsystem", "smart_shutdown"}, call("smartShutdown"), (""), 177) + + -- include zigbee dongle device only + entry({"api", "xqsystem", "device_list_zigbee"}, call("getDeviceListZigbee"), (""), 176) + -- Noflushd + entry({"api", "xqsystem", "noflushd"}, call("getNofStatus"), (""), 178) + entry({"api", "xqsystem", "nof_switch"}, call("nofSwitch"), (""), 179) + entry({"api", "xqsystem", "pred_status"}, call("predownloadInfo"), (""), 180) + entry({"api", "xqsystem", "pred_switch"}, call("predownloadSwitch"), (""), 181) +end + +local LuciHttp = require("luci.http") +local XQConfigs = require("xiaoqiang.common.XQConfigs") +local XQSysUtil = require("xiaoqiang.util.XQSysUtil") +local XQErrorUtil = require("xiaoqiang.util.XQErrorUtil") + +function getInitInfo() + local XQCountryCode = require("xiaoqiang.XQCountryCode") + local XQNetUtil = require("xiaoqiang.util.XQNetUtil") + local XQSysUtil = require("xiaoqiang.util.XQSysUtil") + local XQLanWanUtil = require("xiaoqiang.util.XQLanWanUtil") + local monitor = XQLanWanUtil.getWanMonitorStat() + local connect = 0 + if monitor.WANLINKSTAT == "UP" then + connect = 1 + end + local result = {} + result["code"] = 0 + result["connect"] = connect + result["inited"] = XQSysUtil.getInitInfo() and 1 or 0 + result["bound"] = XQSysUtil.getPassportBindInfo() and 1 or 0 + result["id"] = XQNetUtil.getSN() + result["routerId"] = XQNetUtil.getDeviceId() + result["hardware"] = XQSysUtil.getHardware() + result["romversion"] = XQSysUtil.getRomVersion() + result["modules"] = XQSysUtil.getModulesList() + result["language"] = XQSysUtil.getLang() + result["countrycode"] = XQCountryCode.getCurrentCountryCode() + LuciHttp.write_json(result) +end + +function getFacInfo() + local XQSysUtil = require("xiaoqiang.util.XQSysUtil") + LuciHttp.write_json(XQSysUtil.facInfo()) +end + +function actionLogin() + local result = {} + local init = tonumber(LuciHttp.formvalue("init")) + result["code"] = 0 + if init and init == 1 then + result["url"] = luci.dispatcher.build_url("web", "init", "guide") + else + result["url"] = luci.dispatcher.build_url("web", "home") + end + LuciHttp.write_json(result) +end + +function getToken() + local XQNetUtil = require("xiaoqiang.util.XQNetUtil") + local sid = LuciHttp.formvalue("sid") + local result = {} + result["code"] = 0 + result["token"] = luci.dispatcher.context.urltoken.stok + result["id"] = XQNetUtil.getSN() + result["name"] = XQSysUtil.getRouterName() + LuciHttp.write_json(result) +end + +function renewToken() + local sauth = require "luci.sauth" + local result = {} + local session = sauth.available() + if session then + result["token"] = session.token + else + local token = luci.sys.uniqueid(16) + sauth.write(token, { + user="admin", + token=token, + ltype="1", + secret=luci.sys.uniqueid(16) + }) + result["token"] = token + end + result["code"] = 0 + LuciHttp.write_json(result) +end + +function setInited() + local XQLog = require("xiaoqiang.XQLog") + local client = LuciHttp.formvalue("client") + if client == "ios" then + XQLog.check(0, XQLog.KEY_GEL_INIT_IOS, 1) + elseif client == "android" then + XQLog.check(0, XQLog.KEY_GEL_INIT_ANDROID, 1) + elseif client == "other" then + XQLog.check(0, XQLog.KEY_GEL_INIT_OTHER, 1) + end + local result = {} + local inited = XQSysUtil.setInited() + if not inited then + result["code"] = 1501 + result["msg"] = XQErrorUtil.getErrorMessage(1501) + else + result["code"] = 0 + end + LuciHttp.write_json(result) +end + +function getLanWanSta() + local XQDeviceUtil = require("xiaoqiang.util.XQDeviceUtil") + local result = {} + result["code"] = 0 + result["lan"] = XQDeviceUtil.getWanLanNetworkStatistics("lan") + result["wan"] = XQDeviceUtil.getWanLanNetworkStatistics("wan") + LuciHttp.write_json(result) +end + +function getPassportBindInfo() + local result = {} + local bind = XQSysUtil.getPassportBindInfo() + result["code"] = 0 + if bind then + result["bound"] = 1 + result["uuid"] = bind + else + result["bound"] = 0 + end + LuciHttp.write_json(result) +end + +function setPassportBound() + local uuid = LuciHttp.formvalue("uuid") + local result = {} + local inited = XQSysUtil.setPassportBound(true,uuid) + if not inited then + result["code"] = 1501 + result["msg"] = XQErrorUtil.getErrorMessage(1501) + else + result["code"] = 0 + end + LuciHttp.write_json(result) +end + +function removePassportBindInfo() + local uuid = LuciHttp.formvalue("uuid") + local result = {} + XQSysUtil.setPassportBound(false,uuid) + result["code"] = 0 + LuciHttp.write_json(result) +end + +function getSysInfo() + local result = {} + result["code"] = 0 + result["upTime"] = XQSysUtil.getSysUptime() + result["routerName"] = XQSysUtil.getRouterName() + result["romVersion"] = XQSysUtil.getRomVersion() + result["romChannel"] = XQSysUtil.getChannel() + result["hardware"] = XQSysUtil.getHardware() + LuciHttp.write_json(result) +end + +function getAllInfo() + local XQLanWanUtil = require("xiaoqiang.util.XQLanWanUtil") + local XQWifiUtil = require("xiaoqiang.util.XQWifiUtil") + local result = {} + local monitor = XQLanWanUtil.getWanMonitorStat() + local connect = 0 + if monitor.WANLINKSTAT == "UP" then + connect = 1 + end + result["connect"] = connect + result["wifi"] = XQWifiUtil.getAllWifiInfo() + result["wan"] = XQLanWanUtil.getLanWanInfo("wan") + result["lan"] = XQLanWanUtil.getLanWanInfo("lan") + result["code"] = 0 + result.wifi[1].channel = XQWifiUtil.getWifiWorkChannel(1) + result.wifi[2].channel = XQWifiUtil.getWifiWorkChannel(2) + LuciHttp.write_json(result) +end + +function getStatusInfo() + local XQDeviceUtil = require("xiaoqiang.util.XQDeviceUtil") + local XQLanWanUtil = require("xiaoqiang.util.XQLanWanUtil") + local XQConfigs = require("xiaoqiang.common.XQConfigs") + local XQWifiUtil = require("xiaoqiang.util.XQWifiUtil") + local result = {} + local monitor = XQLanWanUtil.getWanMonitorStat() + if monitor.WANLINKSTAT == "UP" then + result["connect"] = 1 + end + if monitor.VPNLINKSTAT == "UP" then + result["vpn"] = 1 + end + local wifiConCount = {} + table.insert(wifiConCount,#XQWifiUtil.getWifiConnectDeviceList(1)) + table.insert(wifiConCount,#XQWifiUtil.getWifiConnectDeviceList(2)) + local statList = XQDeviceUtil.getDevNetStatisticsList() + if #statList > 0 then + table.sort(statList, function(a, b) return tonumber(a.download) > tonumber(b.download) end) + end + if #statList > XQConfigs.DEVICE_STATISTICS_LIST_LIMIT then + local item = {} + item["mac"] = "" + item["ip"] = "" + for i=1,#statList - XQConfigs.DEVICE_STATISTICS_LIST_LIMIT + 1 do + local deleteElement = table.remove(statList, XQConfigs.DEVICE_STATISTICS_LIST_LIMIT) + item["onlinets"] = deleteElement.onlinets + item["activets"] = deleteElement.activets + item["upload"] = tonumber(deleteElement.upload) + tonumber(item.upload or 0) + item["upspeed"] = tonumber(deleteElement.upspeed) + tonumber(item.upspeed or 0) + item["download"] = tonumber(deleteElement.download) + tonumber(item.download or 0) + item["downspeed"] = tonumber(deleteElement.downspeed) + tonumber(item.downspeed or 0) + item["online"] = deleteElement.online + item["idle"] = deleteElement.idle + item["devname"] = "Others" + item["initail"] = deleteElement.initail + item["maxuploadspeed"] = deleteElement.maxuploadspeed + item["maxdownloadspeed"] = deleteElement.maxdownloadspeed + end + table.insert(statList,item) + end + + result["lanLink"] = XQLanWanUtil.getLanLinkList() + result["count"] = XQDeviceUtil.getConnectDeviceCount() + result["upTime"] = XQSysUtil.getSysUptime() + result["wifiCount"] = wifiConCount + result["wanStatistics"] = XQDeviceUtil.getWanLanNetworkStatistics("wan") + result["devStatistics"] = statList + result["code"] = 0 + LuciHttp.write_json(result) +end + +function getConDevCount() + local XQDeviceUtil = require("xiaoqiang.util.XQDeviceUtil") + local result = {} + result["code"]= 0 + result["count"] = XQDeviceUtil.getConnectDeviceCount() + LuciHttp.write_json(result) +end + +function _savePassword(nonce, oldpwd, newpwd) + local XQSecureUtil = require("xiaoqiang.util.XQSecureUtil") + local code = 0 + local mac = luci.dispatcher.getremotemac() + local checkNonce = XQSecureUtil.checkNonce(nonce, mac) + if checkNonce then + local check = XQSecureUtil.checkUser("admin", nonce, oldpwd) + if check then + if XQSecureUtil.saveCiphertextPwd("admin", newpwd) then + code = 0 + else + code = 1553 + end + else + code = 1552 + end + else + code = 1582 + end + return code +end + +function setPassword() + local XQFunction = require("xiaoqiang.common.XQFunction") + local result = {} + local code + local nonce = LuciHttp.formvalue("nonce") + local oldPassword = LuciHttp.formvalue("oldPwd") + local newPassword = LuciHttp.formvalue("newPwd") + if XQFunction.isStrNil(oldPassword) or XQFunction.isStrNil(newPassword) then + code = 1502 + else + if nonce then + code = _savePassword(nonce, oldPassword, newPassword) + else + local XQSecureUtil = require("xiaoqiang.util.XQSecureUtil") + local check = XQSysUtil.checkSysPassword(oldPassword) or XQSecureUtil.checkPlaintextPwd("admin", oldPassword) + if check then + local setPwd = XQSysUtil.setSysPassword(newPassword) + if setPwd then + code = 0 + else + code = 1553 + end + else + code = 1552 + end + end + end + if code ~= 0 then + result["msg"] = XQErrorUtil.getErrorMessage(code) + end + result["code"] = code + LuciHttp.write_json(result) +end + +function checkRomUpdate() + local XQConfigs = require("xiaoqiang.common.XQConfigs") + local XQNetUtil = require("xiaoqiang.util.XQNetUtil") + local XQSysUtil = require("xiaoqiang.util.XQSysUtil") + local result = {} + local status = {status = 0, percent = 0} + local code = 0 + local check = XQNetUtil.checkUpgrade() + local upgrade = XQSysUtil.checkUpgradeStatus() + if check == false then + code = 1504 + else + code = 0 + result = check + end + result["status"] = status + if code ~= 0 then + result["msg"] = XQErrorUtil.getErrorMessage(code) + end + result["code"] = code + LuciHttp.write_json(result) +end + +-- 直接执行升级脚本 +function upgradeRom() + local XQFunction = require("xiaoqiang.common.XQFunction") + local XQSysUtil = require("xiaoqiang.util.XQSysUtil") + local XQSecureUtil = require("xiaoqiang.util.XQSecureUtil") + + local url = LuciHttp.formvalue("url") + local filesize = tostring(LuciHttp.formvalue("filesize")) + local hash = tostring(LuciHttp.formvalue("hash")) + + local result = {} + local code = 0 + if XQSysUtil.checkBeenUpgraded() then + code = 1577 + elseif XQSysUtil.isUpgrading() then + code = 1568 + elseif not XQSecureUtil.cmdSafeCheck(url) or not XQSecureUtil.cmdSafeCheck(filesize) or not XQSecureUtil.cmdSafeCheck(hash) then + code = 1523 + end + result["code"] = code + if code ~= 0 then + result["msg"] = XQErrorUtil.getErrorMessage(code) + end + LuciHttp.write_json(result) + if code == 0 then + XQFunction.sysLock() + if url and filesize and hash then + XQFunction.forkExec(string.format("/usr/sbin/crontab_rom.sh '%s' '%s' '%s'", url, hash, filesize)) + else + XQFunction.forkExec("/usr/sbin/crontab_rom.sh") + end + end +end + +function cancelUpgrade() + local XQSysUtil = require("xiaoqiang.util.XQSysUtil") + local code = 0 + local result = {} + local succeed = XQSysUtil.cancelUpgrade() + if not succeed then + code = 1579 + result["msg"] = XQErrorUtil.getErrorMessage(code) + end + result["code"] = code + LuciHttp.write_json(result) +end + +function flashRom() + local LuciFs = require("luci.fs") + local XQConfigs = require("xiaoqiang.common.XQConfigs") + local XQFunction = require("xiaoqiang.common.XQFunction") + local XQPreference = require("xiaoqiang.XQPreference") + local XQSysUtil = require("xiaoqiang.util.XQSysUtil") + local LuciUtil = require("luci.util") + local custom = tonumber(LuciHttp.formvalue("custom") or 0) + local result = {} + local code = 0 + local filePath = XQConfigs.ROM_CACHE_FILEPATH + if custom == 1 then + filePath = XQConfigs.CROM_DISK_CACHE_FILEPATH + end + local flashStatus = XQSysUtil.getFlashStatus() + if flashStatus == 1 then + code = 1560 + elseif flashStatus == 2 then + code = 1577 + elseif not LuciFs.access(filePath) then + code = 1507 + elseif not XQSysUtil.verifyImage(filePath) then + code = 1554 + end + XQFunction.ledFlashAlert(false) + if code ~= 0 then + result["msg"] = XQErrorUtil.getErrorMessage(code) + end + result["code"] = code + LuciHttp.write_json(result) + if code == 0 then + LuciHttp.close() + XQFunction.sysLock() + XQFunction.forkFlashRomFile(filePath) + end +end + +function flashStatus() + local XQSysUtil = require("xiaoqiang.util.XQSysUtil") + local result = {} + result["code"] = 0 + result["status"] = XQSysUtil.getFlashStatus() + LuciHttp.write_json(result) +end + +function upgradeStatus() + local XQSysUtil = require("xiaoqiang.util.XQSysUtil") + local result = {} + result["code"] = 0 + result["status"] = XQSysUtil.checkUpgradeStatus() + if result.status == 3 then + local LuciFs = require("luci.fs") + local XQConfigs = require("xiaoqiang.common.XQConfigs") + local XQPreference = require("xiaoqiang.XQPreference") + local XQDownloadUtil = require("xiaoqiang.util.XQDownloadUtil") + local downloadId = XQPreference.get(XQConfigs.PREF_ROM_DOWNLOAD_ID, nil) + if downloadId then + result["percent"] = XQDownloadUtil.downloadPercent(downloadId) + else + result["percent"] = 0 + end + elseif result.status == 5 then + result["percent"] = 100 + end + LuciHttp.write_json(result) +end + +function getRouterName() + local result = {} + result["code"] = 0 + result["routerName"] = XQSysUtil.getRouterName() + LuciHttp.write_json(result) +end + +function setRouterName() + local XQFunction = require("xiaoqiang.common.XQFunction") + local routerName = LuciHttp.xqformvalue("routerName") + local result = {} + local code = 0 + if XQFunction.isStrNil(routerName) then + code = 1502 + else + local newName = XQSysUtil.setRouterName(routerName) + if newName == false then + code = 1503 + else + result["routerName"] = newName + end + end + if code ~= 0 then + result["msg"] = XQErrorUtil.getErrorMessage(code) + end + result["code"] = code + LuciHttp.write_json(result) +end + +function setRouter() + local XQConfigs = require("xiaoqiang.common.XQConfigs") + local XQFunction = require("xiaoqiang.common.XQFunction") + local XQLanWanUtil = require("xiaoqiang.util.XQLanWanUtil") + local XQWifiUtil = require("xiaoqiang.util.XQWifiUtil") + local result = {} + local code = 0 + local msg = {} + local needRestartWifi = false + local nonce = LuciHttp.formvalue("nonce") + local newPwd = LuciHttp.formvalue("newPwd") + local oldPwd = LuciHttp.formvalue("oldPwd") + local wifiPwd = LuciHttp.formvalue("wifiPwd") + local wifi24Ssid = LuciHttp.formvalue("wifi24Ssid") + local wifi50Ssid = LuciHttp.formvalue("wifi50Ssid") + local wanType = LuciHttp.formvalue("wanType") + local pppoeName = LuciHttp.formvalue("pppoeName") + local pppoePwd = LuciHttp.formvalue("pppoePwd") + + XQFunction.nvramSet("Router_unconfigured", "0") + XQFunction.nvramCommit() + + local checkssid = XQWifiUtil.checkSSID(wifi24Ssid,28) + if not XQFunction.isStrNil(wifi24Ssid) and checkssid == 0 then + XQSysUtil.setRouterName(wifi24Ssid) + end + if not XQFunction.isStrNil(newPwd) and not XQFunction.isStrNil(oldPwd) then + if nonce then + code = _savePassword(nonce, oldPwd, newPwd) + else + local check = XQSysUtil.checkSysPassword(oldPwd) + if check then + local succeed = XQSysUtil.setSysPassword(newPwd) + if not succeed then + code = 1515 + end + else + code = 1552 + end + end + if code ~= 0 then + table.insert(msg,XQErrorUtil.getErrorMessage(code)) + end + end + if not XQFunction.isStrNil(wanType) then + local succeed + if wanType == "pppoe" and not XQFunction.isStrNil(pppoeName) and not XQFunction.isStrNil(pppoePwd) then + succeed = XQLanWanUtil.setWanPPPoE(pppoeName,pppoePwd) + elseif wanType == "dhcp" then + succeed = XQLanWanUtil.setWanStaticOrDHCP(wanType) + end + if not succeed then + code = 1518 + table.insert(msg,XQErrorUtil.getErrorMessage(code)) + else + needRestartWifi = true + end + end + if not XQFunction.isStrNil(wifiPwd) and checkssid == 0 then + local succeed1 = XQWifiUtil.setWifiBasicInfo(1, wifi24Ssid, wifiPwd, "mixed-psk", nil, nil, 0) + local succeed2 = XQWifiUtil.setWifiBasicInfo(2, wifi50Ssid, wifiPwd, "mixed-psk", nil, nil, 0) + if succeed1 or succeed2 then + needRestartWifi = true + end + if not succeed1 or not succeed2 then + code = XQWifiUtil.checkWifiPasswd(wifiPwd, "mixed-psk") + table.insert(msg,XQErrorUtil.getErrorMessage(code)) + end + end + if checkssid ~= 0 then + code = checkssid + end + if code ~= 0 then + result["msg"] = XQErrorUtil.getErrorMessage(1519) + result["errorDetails"] = msg + end + XQSysUtil.setSPwd() + XQSysUtil.setInited() + result["code"] = code + LuciHttp.write_json(result) + if needRestartWifi then + LuciHttp.close() + XQFunction.forkRestartWifi() + end +end + +function getDeviceList() + local XQConfigs = require("xiaoqiang.common.XQConfigs") + local XQDeviceUtil = require("xiaoqiang.util.XQDeviceUtil") + local result = {} + result["code"] = 0 + result["mac"] = luci.dispatcher.getremotemac() + result["list"] = XQDeviceUtil.getConnectDeviceList() + LuciHttp.write_json(result) +end + +function getDeviceListZigbee() + local XQConfigs = require("xiaoqiang.common.XQConfigs") + local XQDeviceUtil = require("xiaoqiang.util.XQDeviceUtil") + local XQZigbeeUtil = require("xiaoqiang.util.XQZigbeeUtil") + local result = {} + result["code"] = 0 + result["mac"] = luci.dispatcher.getremotemac() + local list = {} + -- add zigbee device + XQZigbeeUtil.append_yeelink_list(list) + result["list"] = list + LuciHttp.write_json(result) +end + +function isInternetConnect() + local XQLanWanUtil = require("xiaoqiang.util.XQLanWanUtil") + local result = {} + local monitor = XQLanWanUtil.getWanMonitorStat() + local connect = 0 + if monitor.WANLINKSTAT == "UP" then + connect = 1 + end + result["code"] = 0 + result["connect"] = connect + LuciHttp.write_json(result) +end + +function setDeviceNickName() + local XQFunction = require("xiaoqiang.common.XQFunction") + local XQDeviceUtil = require("xiaoqiang.util.XQDeviceUtil") + local LuciDatatypes = require("luci.cbi.datatypes") + local result = {} + local code = 0 + local mac = LuciHttp.formvalue("mac") + local nickName = LuciHttp.formvalue("name") + if XQFunction.isStrNil(mac) or XQFunction.isStrNil(nickName) then + code = 1502 + -- allow none ip device to set nick name (zigbee device) + -- elseif not LuciDatatypes.macaddr(mac) then + -- code = 1508 + else + XQDeviceUtil.saveDeviceName(mac,nickName) + end + if code ~= 0 then + result["msg"] = XQErrorUtil.getErrorMessage(code) + end + result["code"] = code + LuciHttp.write_json(result) +end + + +function _prepare() + local FS = require("nixio.fs") + FS.mkdir(XQConfigs.USERDISK_UPLOAD_DIR, 777) + if not _sane() then + error("Upload Rom Exception: /userdisk/upload path is not sane!") + end +end + +function _sane() + local FS = require("nixio.fs") + local LuciSys = require("luci.sys") + return LuciSys.process.info("uid") + == FS.stat(XQConfigs.USERDISK_UPLOAD_DIR, "uid") +end + +function uploadRom() + local XQConfigs = require("xiaoqiang.common.XQConfigs") + local XQSysUtil = require("xiaoqiang.util.XQSysUtil") + local LuciSys = require("luci.sys") + local LuciFs = require("luci.fs") + + local fp + local code = 0 + if not _sane() then + _prepare() + end + local tmpfile = XQConfigs.USERDISK_UPLOAD_DIR..LuciSys.uniqueid(16) + local fileSize = tonumber(LuciHttp.getenv("CONTENT_LENGTH")) + local canupload = XQSysUtil.checkDiskSpace(fileSize) + LuciHttp.setfilehandler( + function(meta, chunk, eof) + if canupload then + if not fp then + if meta and meta.name == "image" then + fp = io.open(tmpfile, "w") + end + end + if chunk then + fp:write(chunk) + end + if eof then + fp:close() + if LuciFs.access(XQConfigs.CROM_DISK_CACHE_FILEPATH) then + LuciFs.unlink(XQConfigs.CROM_DISK_CACHE_FILEPATH) + end + LuciFs.rename(tmpfile, XQConfigs.CROM_DISK_CACHE_FILEPATH) + end + else + code = 1578 + end + end) + if LuciHttp.formvalue("image") and fp then + code = 0 + else + if code == 0 then + XQLog.log(6, "upload failed, file not exist: "..tostring(filepath)) + code = 1509 + end + end + local result = {} + if code ~= 0 then + result["msg"] = XQErrorUtil.getErrorMessage(code) + end + result["code"] = code + LuciHttp.write_json(result) +end + +function getLangList() + local result = {} + result["code"] = 0 + result["list"] = XQSysUtil.getLangList() + LuciHttp.write_json(result) +end + +function getMainLang() + local result = {} + result["code"] = 0 + result["lang"] = XQSysUtil.getLang() + LuciHttp.write_json(result) +end + +function setLang() + local XQFunction = require("xiaoqiang.common.XQFunction") + local code = 0 + local result = {} + local lang = LuciHttp.formvalue("language") + if XQFunction.isStrNil(lang) then + code = 1502 + end + local succeed = XQSysUtil.setLang() + if not succeed then + code = 1511 + end + if code ~= 0 then + result["msg"] = XQErrorUtil.getErrorMessage(code) + end + result["code"] = code + LuciHttp.write_json(result) +end + +function uploadLogFile() + local XQConfigs = require("xiaoqiang.common.XQConfigs") + local XQNetUtil = require("xiaoqiang.util.XQNetUtil") + local LuciUtil = require("luci.util") + local code = 0 + local result = {} + LuciUtil.exec("/usr/sbin/log_collection.sh") + local succeed = XQNetUtil.uploadLogFile(XQConfigs.LOG_ZIP_FILEPATH,"B") + if not succeed then + code = 1512 + end + result["code"] = code + if code ~= 0 then + result["msg"] = XQErrorUtil.getErrorMessage(code) + end + LuciUtil.exec("rm "..XQConfigs.LOG_ZIP_FILEPATH) + LuciHttp.write_json(result) +end + +function uploadConfigFile() + local XQConfigs = require("xiaoqiang.common.XQConfigs") + local XQNetUtil = require("xiaoqiang.util.XQNetUtil") + local LuciUtil = require("luci.util") + local code = 0 + local result = {} + LuciUtil.exec("/usr/sbin/config_collection.sh") + local succeed = XQNetUtil.uploadConfigFile(XQConfigs.CONFIG_ZIP_FILEPATH) + if not succeed then + code = 1512 + end + result["code"] = code + if code ~= 0 then + result["msg"] = XQErrorUtil.getErrorMessage(code) + end + LuciUtil.exec("rm "..XQConfigs.CONFIG_ZIP_FILEPATH) + LuciHttp.write_json(result) +end + +function configRecovery() + local XQConfigs = require("xiaoqiang.common.XQConfigs") + local XQNetUtil = require("xiaoqiang.util.XQNetUtil") + local LuciUtil = require("luci.util") + local code = 0 + local result = {} + local succeed = XQNetUtil.getConfigFile(XQConfigs.CONFIG_ZIP_FILEPATH) + if not succeed then + code = 1513 + else + LuciUtil.exec("/usr/bin/unzip -o "..XQConfigs.CONFIG_ZIP_FILEPATH.." -d //") + end + result["code"] = code + if code ~= 0 then + result["msg"] = XQErrorUtil.getErrorMessage(code) + end + LuciUtil.exec("/bin/rm "..XQConfigs.CONFIG_ZIP_FILEPATH) + LuciHttp.write_json(result) +end + +function reboot() + local XQLog = require("xiaoqiang.XQLog") + local XQFunction = require("xiaoqiang.common.XQFunction") + local XQLanWanUtil = require("xiaoqiang.util.XQLanWanUtil") + local client = LuciHttp.formvalue("client") + local lanIp = XQLanWanUtil.getLanWanIp("lan") + local result = {} + if client == "web" then + XQLog.check(0, XQLog.KEY_REBOOT, 1) + end + result["code"] = 0 + result["lanIp"] = lanIp + LuciHttp.write_json(result) + LuciHttp.close() + XQFunction.forkReboot() +end + +function reset() + local XQConfigs = require("xiaoqiang.common.XQConfigs") + local XQFunction = require("xiaoqiang.common.XQFunction") + local LuciUtil = require("luci.util") + local LuciJson = require("json") + local format = tonumber(LuciHttp.formvalue("format") or 0) + local code = 0 + local result = {} + if format == 1 then + local formatResult = XQFunction.thrift_tunnel_to_datacenter([[{"api":28}]]) + if formatResult then + if formatResult.code == 0 then + code = 0 + else + code = 1558 + end + else + code = 1559 + end + end + result["code"] = code + if code ~= 0 then + result["msg"] = XQErrorUtil.getErrorMessage(result.code) + end + LuciHttp.write_json(result) + LuciHttp.close() + if result.code == 0 then + -- reset smart controller database and config + XQFunction.thrift_tunnel_to_smarthome_controller([[{"command":"reset_scenes"}]]) + -- set restore default and reboot + XQFunction.forkResetAll() + end +end + +function getSysAvgLoad() + local LuciUtil = require("luci.util") + local XQSysUtil = require("xiaoqiang.util.XQSysUtil") + XQSysUtil.setDetectionTimestamp() + local result = {} + result["code"] = 0 + local avg = LuciUtil.exec("/usr/sbin/sysapi system_info get cpuload") + result["loadavg"] = tonumber(avg) + result["processCount"] = tonumber(LuciUtil.exec("cat /proc/cpuinfo | grep -c 'processor'")) + LuciHttp.write_json(result) + LuciHttp.close() +end + +function setMacFilter() + local XQFunction = require("xiaoqiang.common.XQFunction") + local XQSysUtil = require("xiaoqiang.util.XQSysUtil") + local LuciUtil = require("luci.util") + local LuciDatatypes = require("luci.cbi.datatypes") + local result = {} + local code = 0 + local mac = LuciHttp.formvalue("mac") + local wan = LuciHttp.formvalue("wan") + local lan = LuciHttp.formvalue("lan") + local admin = LuciHttp.formvalue("admin") + local pridisk = LuciHttp.formvalue("pridisk") + + if not XQFunction.isStrNil(mac) and LuciDatatypes.macaddr(mac) then + if wan then + wan = tonumber(wan) == 1 and "1" or "0" + end + if lan then + lan = tonumber(lan) == 1 and "1" or "0" + end + if admin then + admin = tonumber(admin) == 1 and "1" or "0" + end + if pridisk then + pridisk = tonumber(pridisk) == 1 and "1" or "0" + end + XQSysUtil.setMacFilter(mac,lan,wan,admin,pridisk) + else + code = 1508 + end + result["code"] = code + if code ~= 0 then + result["msg"] = XQErrorUtil.getErrorMessage(code) + end + LuciHttp.write_json(result) +end + +function openWps() + local XQWifiUtil = require("xiaoqiang.util.XQWifiUtil") + local result = {} + result["code"] = 0 + result["timestamp"] = XQWifiUtil.openWifiWps() + LuciHttp.write_json(result) +end + +function stopWps() + local XQWifiUtil = require("xiaoqiang.util.XQWifiUtil") + XQWifiUtil.stopWps() + local result = {} + result["code"] = 0 + LuciHttp.write_json(result) +end + +function _checkConnection(mac, try) + local cmac + local XQWifiUtil = require("xiaoqiang.util.XQWifiUtil") + if not mac then + cmac = XQWifiUtil.getWpsConDevMac() + else + cmac = mac + end + if XQWifiUtil.isDeviceWifiConnect(cmac, 1) or XQWifiUtil.isDeviceWifiConnect(cmac, 2) then + return cmac + else + if try > 0 then + os.execute("sleep 3") + _checkConnection(cmac, try - 1) + end + end + return false +end + +function getWpsStatus() + local XQWifiUtil = require("xiaoqiang.util.XQWifiUtil") + local XQPreference = require("xiaoqiang.XQPreference") + local XQConfigs = require("xiaoqiang.common.XQConfigs") + local XQDeviceUtil = require("xiaoqiang.util.XQDeviceUtil") + local result = {} + local status = XQWifiUtil.getWifiWpsStatus() + if status == 2 then + local device = {} + local mac = XQWifiUtil.getWpsConDevMac() + if mac then + if XQWifiUtil.isDeviceWifiConnect(mac, 1) or XQWifiUtil.isDeviceWifiConnect(mac, 2) then + device["mac"] = mac + device["company"] = XQDeviceUtil.getDeviceCompany(mac) + else + local cmac = _checkConnection(mac, 2) + if cmac then + device["mac"] = cmac + device["company"] = XQDeviceUtil.getDeviceCompany(cmac) + result["device"] = device + else + status = 9 + end + end + else + local cmac = _checkConnection(mac, 2) + if cmac then + device["mac"] = cmac + device["company"] = XQDeviceUtil.getDeviceCompany(cmac) + result["device"] = device + else + status = 9 + end + end + end + if status >= 3 and status <= 7 then + status = 3 + end + result["code"] = 0 + result["status"] = status + result["startTime"] = XQPreference.get(XQConfigs.PREF_WPS_TIMESTAMP,"") + result["currentTime"] = tostring(os.time()) + LuciHttp.write_json(result) +end + +function createSandbox() + local LuciUtil = require("luci.util") + local XQConfigs = require("xiaoqiang.common.XQConfigs") + local result = {} + result["code"] = 0 + LuciUtil.exec(XQConfigs.LAMP_CREATE_SANDBOX) + LuciHttp.write_json(result) +end + +function mountThings() + local LuciUtil = require("luci.util") + local XQConfigs = require("xiaoqiang.common.XQConfigs") + local result = {} + result["code"] = 0 + LuciUtil.exec(XQConfigs.LAMP_MOUNT_THINGS) + LuciHttp.write_json(result) +end + +function umountThings() + local LuciUtil = require("luci.util") + local XQConfigs = require("xiaoqiang.common.XQConfigs") + local result = {} + result["code"] = 0 + LuciUtil.exec(XQConfigs.LAMP_UMOUNT_THINGS) + LuciHttp.write_json(result) +end + +function startDropbear() + local LuciUtil = require("luci.util") + local XQConfigs = require("xiaoqiang.common.XQConfigs") + local result = {} + result["code"] = 0 + LuciUtil.exec(XQConfigs.LAMP_START_DROPBEAR) + LuciHttp.write_json(result) +end + +function stopDropbear() + local LuciUtil = require("luci.util") + local XQConfigs = require("xiaoqiang.common.XQConfigs") + local result = {} + result["code"] = 0 + LuciUtil.exec(XQConfigs.LAMP_STOP_DROPBEAR) + LuciHttp.write_json(result) +end + +function isSandboxCreated() + local LuciUtil = require("luci.util") + local XQConfigs = require("xiaoqiang.common.XQConfigs") + local result = {} + result["code"] = 0 + result["isSandboxCreated"] = (0 == tonumber(os.execute(XQConfigs.LAMP_IS_SANDBOX_CREATED))) + LuciHttp.write_json(result) +end + +function areThingsMounted() + local LuciUtil = require("luci.util") + local XQConfigs = require("xiaoqiang.common.XQConfigs") + local result = {} + result["code"] = 0 + result["areThingsMounted"] = (0 == tonumber(os.execute(XQConfigs.LAMP_ARE_THINGS_MOUNTED))) + LuciHttp.write_json(result) +end + +function isDropbearStarted() + local LuciUtil = require("luci.util") + local XQConfigs = require("xiaoqiang.common.XQConfigs") + local result = {} + result["code"] = 0 + result["isDropbearStarted"] = (0 == tonumber(os.execute(XQConfigs.LAMP_IS_DROPBEAR_STARTED))) + LuciHttp.write_json(result) +end + +function stopNginx() + local LuciUtil = require("luci.util") + local XQConfigs = require("xiaoqiang.common.XQConfigs") + local result = {} + result["code"] = 0 + LuciUtil.exec(XQConfigs.NGINX_CACHE_STOP) + LuciHttp.write_json(result) +end + +function startNginx() + local LuciUtil = require("luci.util") + local XQConfigs = require("xiaoqiang.common.XQConfigs") + local result = {} + result["code"] = 0 + LuciUtil.exec(XQConfigs.NGINX_CACHE_START) + LuciHttp.write_json(result) +end + +function nginxCacheStatus() + local LuciUtil = require("luci.util") + local XQConfigs = require("xiaoqiang.common.XQConfigs") + local result = {} + result["code"] = 0 + result["status"] = 1 + local status = LuciUtil.exec(XQConfigs.NGINX_CACHE_STATUS) + if status then + result["status"] = LuciUtil.trim(status) == "NGINX_CACHE=off" and 0 or 1 + end + LuciHttp.write_json(result) +end + +function checkRouterNamePending() + local XQConfigs = require("xiaoqiang.common.XQConfigs") + local cmd = XQConfigs.THRIFT_TO_MQTT_GET_DEVICEID + local LuciUtil = require("luci.util") + local result = {} + result["code"] = 0 + result['pending'] = XQSysUtil.getRouterNamePending() + result["routerId"] = LuciUtil.exec(cmd) + result['routerName'] = XQSysUtil.getRouterName() + LuciHttp.write_json(result) +end + +function clearRouterNamePending() + XQSysUtil.setRouterNamePending('0') + local result = {} + result["code"] = 0 + LuciHttp.write_json(result) +end + +function redirectUrl() + local XQSecureUtil = require("xiaoqiang.util.XQSecureUtil") + local cookieValue = LuciHttp.getcookie("psp") + local result = {} + result["code"] = 0 + if cookieValue then + local loginType = cookieValue:match("|||(%S)|||") + result["redirectUrl"] = "http://miwifi.com/cgi-bin/luci/web/home?redirectKey="..XQSecureUtil.generateRedirectKey(loginType) + else + result["redirectUrl"] = "http://miwifi.com/cgi-bin/luci/web/home?redirectKey="..XQSecureUtil.generateRedirectKey(2) + end + if result.code ~= 0 then + result["msg"] = XQErrorUtil.getErrorMessage(result.code) + end + LuciHttp.write_json(result) +end + +function mainStatusForApp() + local XQFunction = require("xiaoqiang.common.XQFunction") + local XQSysUtil = require("xiaoqiang.util.XQSysUtil") + local XQDeviceUtil = require("xiaoqiang.util.XQDeviceUtil") + local XQZigbeeUtil = require("xiaoqiang.util.XQZigbeeUtil") + local result = {} + local lan = XQDeviceUtil.getWanLanNetworkStatistics("lan") + local wan = XQDeviceUtil.getWanLanNetworkStatistics("wan") + local count = XQFunction.thrift_tunnel_to_smarthome_controller([[{"command":"get_scene_count"}]]) + if count and count.code == 0 then + result["smartSceneCount"] = count.count + else + result["smartSceneCount"] = 0 + end + -- userdisk + local disk = XQFunction.thrift_tunnel_to_datacenter([[{"api":26}]]) + if disk and disk.code == 0 then + result["useableSpace"] = math.floor(tonumber(disk.free) / 1024) + else + result["useableSpace"] = 0 + end + -- plugin + local plugin = XQFunction.thrift_tunnel_to_datacenter([[{"api":601}]]) + if plugin and plugin.code == 0 then + result["installedPluginCount"] = #plugin.data + else + result["installedPluginCount"] = 0 + end + -- downloading + local downloads = 0 + local downloading = 0 + local download = XQFunction.thrift_tunnel_to_datacenter([[{"api":503}]]) + if download and download.code == 0 then + table.foreach(download.uncompletedList, + function(i,v) + downloads = downloads + 1 + if v.downloadStatus == 1 then + downloading = downloading + 1 + end + end + ) + end + -- zigbee + local zigbeecount = XQZigbeeUtil.get_zigbee_count(); + result["code"] = 0 + result["connectDeviceCount"] = zigbeecount + XQDeviceUtil.getConnectDeviceCount() + + + result["upTime"] = XQSysUtil.getSysUptime() + result["maxWanSpeed"] = tonumber(wan.maxdownloadspeed) + result["maxLanSpeed"] = tonumber(lan.maxdownloadspeed) + result["wanSpeed"] = tonumber(wan.downspeed) + result["lanSpeed"] = tonumber(lan.downspeed) + result["hasDownloading"] = downloading > 0 and 1 or 0 + result["downloadingCount"] = downloads + LuciHttp.write_json(result) +end + +--[[ + filter : lan/wan/admin +]]-- +function getMacfilterMode() + local XQSysUtil = require("xiaoqiang.util.XQSysUtil") + local code = 0 + local result = {} + + local filter = LuciHttp.formvalue("filter") or "lan" + local mode = XQSysUtil.getMacfilterMode(filter) + if mode then + result["mode"] = mode + else + code = 1574 + end + result["code"] = code + if result.code ~= 0 then + result["msg"] = XQErrorUtil.getErrorMessage(result.code) + end + LuciHttp.write_json(result) +end + +--[[ + filter : lan/wan/admin + mode : 0/1 (whitelist/blacklist) +]]-- +function setMacfilterMode() + local XQSysUtil = require("xiaoqiang.util.XQSysUtil") + local code = 0 + local result = {} + + local filter = LuciHttp.formvalue("filter") or "lan" + local mode = tonumber(LuciHttp.formvalue("mode") or 0) + local setMode = XQSysUtil.setMacfilterMode(filter,mode) + if not setMode then + code = 1575 + end + result["code"] = code + if result.code ~= 0 then + result["msg"] = XQErrorUtil.getErrorMessage(result.code) + end + LuciHttp.write_json(result) +end + +function shutdown() + local XQFunction = require("xiaoqiang.common.XQFunction") + local result = {} + result["code"] = 0 + LuciHttp.write_json(result) + LuciHttp.close() + XQFunction.forkShutdown() +end + +function upnpList() + local XQUPnPUtil = require("xiaoqiang.util.XQUPnPUtil") + local result = {} + result["code"] = 0 + result["status"] = XQUPnPUtil.getUPnPStatus() and 1 or 0 + local upnp = XQUPnPUtil.getUPnPList() + if upnp then + result["list"] = upnp + else + result["list"] = {} + end + LuciHttp.write_json(result) +end + +function upnpSwitch() + local XQLog = require("xiaoqiang.XQLog") + local XQUPnPUtil = require("xiaoqiang.util.XQUPnPUtil") + local switch = tonumber(LuciHttp.formvalue("switch") or 1) + local result = {} + XQLog.check(0, XQLog.KEY_FUNC_UPNP, switch == 1 and 0 or 1) + XQUPnPUtil.switchUPnP(switch == 1) + result["code"] = 0 + LuciHttp.write_json(result) +end + +function appLimit() + local XQQoSUtil = require("xiaoqiang.util.XQQoSUtil") + local info = XQQoSUtil.appInfo() + info.code = 0 + LuciHttp.write_json(info) +end + +function appLimitSwitch() + local XQLog = require("xiaoqiang.XQLog") + local XQQoSUtil = require("xiaoqiang.util.XQQoSUtil") + local switch = tonumber(LuciHttp.formvalue("switch") or 1) + local result = {} + XQLog.check(0, XQLog.KEY_FUNC_APPQOS, switch == 1 and 0 or 1) + XQQoSUtil.appSpeedlimitSwitch(switch == 1) + result["code"] = 0 + LuciHttp.write_json(result) +end + +function setAppLimit() + local XQQoSUtil = require("xiaoqiang.util.XQQoSUtil") + local result = {} + local xlmaxdownload = LuciHttp.formvalue("xlmaxdownload") + local xlmaxupload = LuciHttp.formvalue("xlmaxupload") + local kpmaxdownload = LuciHttp.formvalue("kpmaxdownload") + local kpmaxupload = LuciHttp.formvalue("kpmaxupload") + XQQoSUtil.setXunlei(xlmaxdownload, xlmaxupload) + XQQoSUtil.setKuaipan(kpmaxdownload, kpmaxupload) + XQQoSUtil.reload() + result["code"] = 0 + LuciHttp.write_json(result) +end + +function vpnInfo() + local XQVPNUtil = require("xiaoqiang.util.XQVPNUtil") + local result = XQVPNUtil.getVPNInfo("vpn") + result["code"] = 0 + LuciHttp.write_json(result) +end + +function setVpn() + local XQLog = require("xiaoqiang.XQLog") + local XQVPNUtil = require("xiaoqiang.util.XQVPNUtil") + local code = 0 + local result = {} + local server = LuciHttp.formvalue("server") + local username = LuciHttp.formvalue("username") + local password = LuciHttp.formvalue("password") + local proto = LuciHttp.formvalue("proto") + local auto = LuciHttp.formvalue("auto") + local set = XQVPNUtil.setVpn("vpn", server, username, password, proto, auto) + if proto and string.upper(proto) == "PPTP" then + XQLog.check(0, XQLog.KEY_FUNC_PPTP, 0) + elseif proto and string.upper(proto) == "L2TP" then + XQLog.check(0, XQLog.KEY_FUNC_L2TP, 0) + end + if set then + code = 0 + else + code = 1583 + end + result["code"] = code + if result.code ~= 0 then + result["msg"] = XQErrorUtil.getErrorMessage(result.code) + end + LuciHttp.write_json(result) +end + +function _vpnErrorCodeHelper(code) + local errorA = { + ["507"] = 1,["691"] = 1,["509"] = 1,["514"] = 1,["520"] = 1, + ["646"] = 1,["647"] = 1,["648"] = 1,["649"] = 1,["691"] = 1, + ["646"] = 1 + } + local errorB = { + ["516"] = 1,["650"] = 1,["601"] = 1,["510"] = 1 + } + local errorC = { + ["501"] = 1,["502"] = 1,["503"] = 1,["504"] = 1,["505"] = 1, + ["506"] = 1,["507"] = 1,["508"] = 1,["511"] = 1,["512"] = 1, + ["515"] = 1,["517"] = 1,["518"] = 1,["519"] = 1 + } + local errcode = tostring(code) + if errcode then + if errorA[errcode] then + return 1584 + end + if errorB[errcode] then + return 1585 + end + if errorC[errcode] then + return 1586 + end + return 1584 + end +end + +-- status: 0 connected 1 connecting 2 failed 3 close 4 none +function vpnStatus() + local XQVPNUtil = require("xiaoqiang.util.XQVPNUtil") + local status = XQVPNUtil.vpnStatus() + local result = {} + if status then + local up = status.up + local autostart = status.autostart + local uptime = tonumber(status.uptime) + local stat = status.stat + if up then + result["status"] = 0 + result["uptime"] = uptime + else + if autostart then + if stat and stat.code ~= 0 then + result["status"] = 2 + result["uptime"] = 0 + result["errcode"] = stat.code + result["errmsg"] = XQErrorUtil.getErrorMessage(_vpnErrorCodeHelper(stat.code)).." "..tostring(stat.code) + else + result["status"] = 1 + result["uptime"] = 0 + end + else + result["status"] = 3 + result["uptime"] = 0 + end + end + else + result["status"] = 4 + result["uptime"] = 0 + end + result["code"] = 0 + LuciHttp.write_json(result) +end + +function vpnSwitch() + local XQVPNUtil = require("xiaoqiang.util.XQVPNUtil") + local conn = tonumber(LuciHttp.formvalue("conn")) + local result = {} + if conn and conn == 1 then + XQVPNUtil.vpnSwitch(true) + else + XQVPNUtil.vpnSwitch(false) + end + result["code"] = 0 + LuciHttp.write_json(result) +end + +function getDeviceMacaddr() + local remoteaddr = luci.http.getenv("REMOTE_ADDR") or "" + local result = {} + local code = 0 + if remoteaddr ~= "127.0.0.1" then + result["mac"] = luci.dispatcher.getremotemac() + else + code = 1587 + end + result["code"] = code + if result.code ~= 0 then + result["msg"] = XQErrorUtil.getErrorMessage(result.code) + end + LuciHttp.write_json(result) +end + +function getDetectionTimestamp() + local XQSysUtil = require("xiaoqiang.util.XQSysUtil") + local result = {} + result["code"] = 0 + result["timestamp"] = XQSysUtil.getDetectionTimestamp() + result["currentTime"] = tostring(os.time()) + LuciHttp.write_json(result) +end + +function getWifiLog() + local XQSysUtil = require("xiaoqiang.util.XQSysUtil") + local result = {} + XQSysUtil.getWifiLog() + result["code"] = 0 + LuciHttp.write_json(result) +end + +function sysRecovery() + local XQFunction = require("xiaoqiang.common.XQFunction") + local XQWifiUtil = require("xiaoqiang.util.XQWifiUtil") + local XQLanWanUtil = require("xiaoqiang.util.XQLanWanUtil") + local ssid = LuciHttp.formvalue("ssid") + local enc = LuciHttp.formvalue("enc") + local key = LuciHttp.formvalue("pwd") + local wanType = LuciHttp.formvalue("wanType") + local pppoeName = LuciHttp.formvalue("pppoeName") + local pppoePwd = LuciHttp.formvalue("pppoePwd") + if ssid then + XQWifiUtil.setWifiBasicInfo(1, ssid, key, enc, nil, nil, 0) + XQWifiUtil.setWifiBasicInfo(2, ssid.."_5G", key, enc, nil, nil, 0) + end + XQFunction.forkRestartWifi() + if wanType == "pppoe" then + XQLanWanUtil.setWanPPPoE(pppoeName,pppoePwd,nil,nil,nil) + elseif wanType == "dhcp" then + XQLanWanUtil.setWanStaticOrDHCP(wanType,nil,nil,nil,nil,nil,nil) + end + local result = {} + result["code"] = 0 + LuciHttp.write_json(result) +end +--flag定时wifi开关 +function smartShutdown() + local XQFunction = require("xiaoqiang.common.XQFunction") + local result = {} + local code = 0 + local delay1 = LuciHttp.formvalue("delay1") + local delay2 = LuciHttp.formvalue("delay2") + if delay1 and delay2 then + XQFunction.forkShutdownAndRebootWithDelay(delay1, delay2) + else + code = 1502 + end + result["code"] = code + if result.code ~= 0 then + result["msg"] = XQErrorUtil.getErrorMessage(result.code) + end + LuciHttp.write_json(result) +end + +function getNofStatus() + local XQSysUtil = require("xiaoqiang.util.XQSysUtil") + local status = tonumber(XQSysUtil.noflushdStatus()) + if status == 0 then + status = 1 + else + status = 0 + end + local result = { + ["code"] = 0, + ["status"] = status + } + LuciHttp.write_json(result) +end + +function nofSwitch() + local XQLog = require("xiaoqiang.XQLog") + local XQSysUtil = require("xiaoqiang.util.XQSysUtil") + local switch = tonumber(LuciHttp.formvalue("switch")) or 0 + local result = {} + local success = XQSysUtil.noflushdSwitch(switch == 1 and true or false) + XQLog.check(0, XQLog.KEY_FUNC_NOFLUSHED, switch == 1 and 0 or 1) + if switch == 1 then + XQLog.check(0, XQLog.KEY_DISKSLEEP_OPEN, 1) + else + XQLog.check(0, XQLog.KEY_DISKSLEEP_CLOSE, 1) + end + if success then + result["code"] = 0 + else + result["code"] = 1606 + end + if result.code ~= 0 then + result["msg"] = XQErrorUtil.getErrorMessage(result.code) + end + LuciHttp.write_json(result) +end + +function predownloadInfo() + local Predownload = require("xiaoqiang.module.XQPredownload") + local result = {} + local info = Predownload.predownloadInfo() + result["code"] = 0 + result["status"] = info.enable + result["priority"] = info.priority + LuciHttp.write_json(result) +end + +function predownloadSwitch() + local Predownload = require("xiaoqiang.module.XQPredownload") + local switch = tonumber(LuciHttp.formvalue("switch")) or 0 + local result = {} + local success = Predownload.switch(switch == 1 and true or false) + if success then + result["code"] = 0 + else + result["code"] = 1606 + end + if result.code ~= 0 then + result["msg"] = XQErrorUtil.getErrorMessage(result.code) + end + LuciHttp.write_json(result) +end diff --git a/Mi_Lua/luci/controller/api/xqtunnel.lua b/Mi_Lua/luci/controller/api/xqtunnel.lua new file mode 100644 index 0000000..87e348e --- /dev/null +++ b/Mi_Lua/luci/controller/api/xqtunnel.lua @@ -0,0 +1,53 @@ +module("luci.controller.api.xqtunnel", package.seeall) + +function index() + local page = node("api","xqtunnel") + page.target = firstchild() + page.title = ("") + page.order = 300 + page.sysauth = "admin" + page.sysauth_authenticator = "jsonauth" + page.index = true + entry({"api", "xqtunnel", "request"}, call("tunnelRequest"), _(""), 301) +end + +local LuciHttp = require("luci.http") +local XQConfigs = require("xiaoqiang.common.XQConfigs") + +local base64chars = { + ['A']=true,['B']=true,['C']=true,['D']=true, + ['E']=true,['F']=true,['G']=true,['H']=true, + ['I']=true,['J']=true,['K']=true,['L']=true, + ['M']=true,['N']=true,['O']=true,['P']=true, + ['Q']=true,['R']=true,['S']=true,['T']=true, + ['U']=true,['V']=true,['W']=true,['X']=true, + ['Y']=true,['Z']=true,['a']=true,['b']=true, + ['c']=true,['d']=true,['e']=true,['f']=true, + ['g']=true,['h']=true,['i']=true,['j']=true, + ['k']=true,['l']=true,['m']=true,['n']=true, + ['o']=true,['p']=true,['q']=true,['r']=true, + ['s']=true,['t']=true,['u']=true,['v']=true, + ['w']=true,['x']=true,['y']=true,['z']=true, + ['0']=true,['1']=true,['2']=true,['3']=true, + ['4']=true,['5']=true,['6']=true,['7']=true, + ['8']=true,['9']=true,['-']=true,['_']=true, + ['+']=true,['/']=true,['=']=true +} + +local function base64filter(input) + local result = "" + for i = 1, #input do + local c = input:sub(i,i) + if base64chars[c] ~= nil and base64chars[c] then + result = result .. c + end + end + return result +end + +function tunnelRequest() + local payload = LuciHttp.formvalue("payloadB64") + local cmd = XQConfigs.TUNNEL_TOOL % base64filter(payload) + local LuciUtil = require("luci.util") + LuciHttp.write(LuciUtil.exec(cmd)) +end diff --git a/Mi_Lua/luci/controller/dispatch/index.lua b/Mi_Lua/luci/controller/dispatch/index.lua new file mode 100644 index 0000000..24eabdd --- /dev/null +++ b/Mi_Lua/luci/controller/dispatch/index.lua @@ -0,0 +1,18 @@ +module("luci.controller.dispatch.index", package.seeall) + +function index() + local root = node() + if not root.target then + root.target = alias("dispatch") + root.index = true + end + local page = node("dispatch") + page.target = firstchild() + page.title = _("") + page.order = 1 + page.sysauth = "admin" + page.mediaurlbase = "/xiaoqiang/dispatch" + page.sysauth_authenticator = "htmlauth" + page.index = true + entry({"dispatch"}, template("index"), _("跳转"), 1, 0x09) +end \ No newline at end of file diff --git a/Mi_Lua/luci/controller/firewall.lua b/Mi_Lua/luci/controller/firewall.lua new file mode 100644 index 0000000..51f05b3 --- /dev/null +++ b/Mi_Lua/luci/controller/firewall.lua @@ -0,0 +1,23 @@ +module("luci.controller.firewall", package.seeall) + +function index() +-- entry({"admin", "network", "firewall"}, +-- alias("admin", "network", "firewall", "zones"), +-- _("Firewall"), 60) +-- +-- entry({"admin", "network", "firewall", "zones"}, +-- arcombine(cbi("firewall/zones"), cbi("firewall/zone-details")), +-- _("General Settings"), 10).leaf = true +-- +-- entry({"admin", "network", "firewall", "forwards"}, +-- arcombine(cbi("firewall/forwards"), cbi("firewall/forward-details")), +-- _("Port Forwards"), 20).leaf = true +-- +-- entry({"admin", "network", "firewall", "rules"}, +-- arcombine(cbi("firewall/rules"), cbi("firewall/rule-details")), +-- _("Traffic Rules"), 30).leaf = true +-- +-- entry({"admin", "network", "firewall", "custom"}, +-- cbi("firewall/custom"), +-- _("Custom Rules"), 40).leaf = true +end diff --git a/Mi_Lua/luci/controller/mobile/index.lua b/Mi_Lua/luci/controller/mobile/index.lua new file mode 100644 index 0000000..3e8ba3f --- /dev/null +++ b/Mi_Lua/luci/controller/mobile/index.lua @@ -0,0 +1,32 @@ +module("luci.controller.mobile.index", package.seeall) +function index() + local root = node() + if not root.target then + root.target = alias("mobile") + root.index = true + end + local page = node("mobile") + page.target = firstchild() + page.title = _("") + page.order = 110 + page.sysauth = "admin" + page.mediaurlbase = "/xiaoqiang/mobile" + page.sysauth_authenticator = "htmlauth_moblie" + page.index = true + entry({"mobile"}, template("mobile/home"), _("首页"), 1, 0x08) + entry({"mobile", "logout"}, call("action_logout"), 2, 0x09) + entry({"mobile", "hello"}, template("mobile/init/hello"), _("初始化欢迎界面"), 3, 0x09) + entry({"mobile", "agreement"}, template("mobile/init/agreement"), _("查看协议"), 4, 0x09) + entry({"mobile", "guide"}, template("mobile/init/guide"), _("初始化引导"), 5, 0x08) +end + +function action_logout() + local dsp = require "luci.dispatcher" + local sauth = require "luci.sauth" + if dsp.context.authsession then + sauth.kill(dsp.context.authsession) + dsp.context.urltoken.stok = nil + end + luci.http.header("Set-Cookie", "sysauth=; path=" .. dsp.build_url()) + luci.http.redirect(luci.dispatcher.build_url().."/mobile") +end diff --git a/Mi_Lua/luci/controller/service/datacenter.lua b/Mi_Lua/luci/controller/service/datacenter.lua new file mode 100644 index 0000000..c12f14a --- /dev/null +++ b/Mi_Lua/luci/controller/service/datacenter.lua @@ -0,0 +1,382 @@ +module("luci.controller.service.datacenter", package.seeall) + +function index() + local page = node("service","datacenter") + page.target = firstchild() + page.title = ("") + page.order = nil + page.sysauth = "admin" + page.sysauth_authenticator = "jsonauth" + page.index = true + entry({"service", "datacenter", "download_file"}, call("downloadFile"), _(""), nil, 0x11) + entry({"service", "datacenter", "device_id"}, call("getDeviceID"), _(""), nil, 0x11) + entry({"service", "datacenter", "download_info"}, call("getDownloadInfo"), _(""), nil, 0x11) + entry({"service", "datacenter", "upload_file"}, call("uploadFile"), _(""), nil, 0x11) + entry({"service", "datacenter", "batch_download_info"}, call("getBatchDownloadInfo"), _(""), nil, 0x11) + entry({"service", "datacenter", "config_info"}, call("getConfigInfo"), _(""), nil, 0x11) + entry({"service", "datacenter", "set_config"}, call("setConfigInfo"), _(""), nil, 0x11) + entry({"service", "datacenter", "plugin_enable"}, call("enablePlugin"), _(""), nil, 0x11) + entry({"service", "datacenter", "plugin_download_info"}, call("pluginDownloadInfo"), _(""), nil, 0x11) + entry({"service", "datacenter", "plugin_disable"}, call("disablePlugin"), _(""), nil, 0x11) + entry({"service", "datacenter", "plugin_control"}, call("controlPlugin"), _(""), nil, 0x11) + entry({"service", "datacenter", "download_delete"}, call("deleteDownload"), _(""), nil, 0x11) + entry({"service", "datacenter", "get_plugin_status"}, call("pluginStatus"), _(""), nil, 0x11) + entry({"service", "datacenter", "get_connected_device"}, call("connectedDevice"), _(""), nil, 0x11) + entry({"service", "datacenter", "get_router_mac"}, call("getMac"), _(""), nil, 0x11) + entry({"service", "datacenter", "set_wan_access"}, call("setWanAccess"), _(""), nil, 0x11) + entry({"service", "datacenter", "get_router_info"}, call("getRouterInfo"), _(""), nil, 0x11) + entry({"service", "datacenter", "xunlei_notify"}, call("xunleiNotify"), _(""), nil, 0x11) +end + +local LuciHttp = require("luci.http") +local XQConfigs = require("xiaoqiang.common.XQConfigs") +local ServiceErrorUtil = require("service.util.ServiceErrorUtil") + +function xunleiNotify() + local payload = {} + payload["api"] = 519 + payload["info"] = LuciHttp.formvalue("tasks") + tunnelRequestDatacenter(payload) +end + +function tunnelRequestDatacenter(payload) + local LuciJson = require("cjson") + local LuciUtil = require("luci.util") + local XQCryptoUtil = require("xiaoqiang.util.XQCryptoUtil") + payload = LuciJson.encode(payload) + payload = XQCryptoUtil.binaryBase64Enc(payload) + local cmd = XQConfigs.THRIFT_TUNNEL_TO_DATACENTER % payload + LuciHttp.write(LuciUtil.exec(cmd)) +end + +function requestDatacenter(payload) + local LuciJson = require("cjson") + local LuciUtil = require("luci.util") + local XQCryptoUtil = require("xiaoqiang.util.XQCryptoUtil") + payload = LuciJson.encode(payload) + payload = XQCryptoUtil.binaryBase64Enc(payload) + local cmd = XQConfigs.THRIFT_TUNNEL_TO_DATACENTER % payload + return LuciUtil.exec(cmd) +end + +function downloadFile() + local payload = {} + payload["api"] = 1101 + payload["appid"] = LuciHttp.formvalue("appId") + payload["path"] = LuciHttp.formvalue("path") + payload["url"] = LuciHttp.formvalue("url") + payload["name"] = LuciHttp.formvalue("downloadName") + payload["tag"] = LuciHttp.formvalue("tag") + payload["hidden"] = false + if LuciHttp.formvalue("hidden") == "true" then + payload["hidden"] = true + end + + payload["redownload"] = 0 + if LuciHttp.formvalue("redownload") == "1" then + payload["redownload"] = 1 + end + + payload["dupId"] = LuciHttp.formvalue("dupId") + tunnelRequestDatacenter(payload) +end + +function setWanAccess() + local payload = {} + payload["api"] = 618 + payload["appid"] = LuciHttp.formvalue("appId") + payload["mac"] = LuciHttp.formvalue("mac") + payload["enable"] = false + if LuciHttp.formvalue("enable") == "true" then + payload["enable"] = true + end + tunnelRequestDatacenter(payload) +end + +function getDeviceID() + local payload = {} + payload["api"] = 1103 + payload["appid"] = LuciHttp.formvalue("appId") + tunnelRequestDatacenter(payload) +end + +function getMac() + local payload = {} + payload["api"] = 617 + payload["appid"] = LuciHttp.formvalue("appId") + tunnelRequestDatacenter(payload) +end + +function getRouterInfo() + local payload = {} + payload["api"] = 622 + payload["appid"] = LuciHttp.formvalue("appId") + tunnelRequestDatacenter(payload) +end + +function getOperateDeviceID() + local payload = {} + payload["api"] = 1103 + payload["appid"] = LuciHttp.formvalue("appId") + + local result = requestDatacenter(payload) + if result then + local LuciJson = require("cjson") + result = LuciJson.decode(result) + if result then + if result.code == 0 then + local deviceid = result["deviceid"] + if deviceid then + return 0, deviceid + end + elseif result.code == 5 then + return 5, nil + end + end + end + + return 1559, nil +end + +function urlEncode(url) + if url then + url = string.gsub(url, "\n", "\r\n") + url = string.gsub(url, "([^0-9a-zA-Z/])", + function(c) return string.format ("%%%02X", string.byte(c)) end) + end + return url +end + +function generateUrlFromPath(path) + if path then + path = urlEncode(path) + local url, count = string.gsub (path, "^/userdisk/data/", "http://miwifi.com/api-third-party/download/public/") + if count == 1 then + return url + end + + url, count = string.gsub (path, "^/userdisk/appdata/", "http://miwifi.com/api-third-party/download/private/") + if count == 1 then + return url + end + + url, count = string.gsub (path, "^/extdisks/", "http://miwifi.com/api-third-party/download/extdisks/") + if count == 1 then + return url + end + end + + return nil +end + +function generateResponseFromCode(code) + local response = {} + response["code"] = code + response["msg"] = ServiceErrorUtil.getErrorMessage(code) + return response +end + +function getDownloadInfo() + local LuciJson = require("cjson") + local payload = {} + payload["api"] = 1102 + payload["appid"] = LuciHttp.formvalue("appId") + payload["deviceId"] = LuciHttp.formvalue("deviceId") + payload["downloadId"] = LuciHttp.formvalue("downloadId") + payload["hidden"] = false + if LuciHttp.formvalue("hidden") == "true" then + payload["hidden"] = true + end + + local response = {} + local result = requestDatacenter(payload) + if result then + result = LuciJson.decode(result) + if result and result.code == 0 then + local url = generateUrlFromPath(result["path"]) + if url then + response["code"] = result["code"] + response["msg"] = result["msg"] + response["url"] = url + else + response = generateResponseFromCode(1559) + end + else + response = result + end + else + response = generateResponseFromCode(1559) + end + + LuciHttp.write_json(response) + LuciHttp.close() +end + +function uploadFile() + local fp + local log = require("xiaoqiang.XQLog") + local fs = require("luci.fs") + local tmpfile = "/userdisk/upload.tmp" + if fs.isfile(tmpfile) then + fs.unlink(tmpfile) + end + + local filename + LuciHttp.setfilehandler( + function(meta, chunk, eof) + if not fp then + if meta and meta.name == "file" then + fp = io.open(tmpfile, "w") + filename = meta.file + filename = string.gsub(filename, "+", " ") + filename = string.gsub(filename, "%%(%x%x)", + function(h) + return string.char(tonumber(h, 16)) + end) + filename = filename.gsub(filename, "\r\n", "\n") + end + end + if chunk then + fp:write(chunk) + end + if eof then + fp:close() + end + end + ) + + local code, deviceId = getOperateDeviceID() + if code ~= 0 then + return LuciHttp.write_json(generateResponseFromCode(code)) + end + + local path + local appid = LuciHttp.formvalue("appId") + local saveType = LuciHttp.formvalue("saveType") + if saveType == "public" then + path = "/userdisk/data/上传/" + elseif saveType == "private" then + path = "/userdisk/appdata/" .. appid .. "/" + else + return LuciHttp.write_json(generateResponseFromCode(3)) + end + fs.mkdir(path, true) + + local savename = fs.basename(filename) + if fs.isfile(path .. savename) then + local basename = savename + local index = basename:match(".+()%.%w+$") + if index then + basename = basename:sub(1, index - 1) + end + + local extension = savename:match(".+%.(%w+)$") + for i = 1, 100, 1 do + local tmpname = basename .. "(" .. i .. ")" + if extension then + tmpname = tmpname .. "." .. extension + end + if not fs.isfile(path .. tmpname) then + savename = tmpname + break + end + end + end + local dest = path .. savename + log.log("dest=" .. dest) + fs.rename(tmpfile, dest) + + local response = {} + response["code"] = 0 + response["url"] = generateUrlFromPath(dest) + response["deviceId"] = deviceId + response["msg"] = "" + LuciHttp.write_json(response) + LuciHttp.close() +end + +function getBatchDownloadInfo() + local payload = {} + payload["api"] = 1105 + payload["appid"] = LuciHttp.formvalue("appId") + payload["ids"] = LuciHttp.formvalue("ids") + payload["hidden"] = false + if LuciHttp.formvalue("hidden") == "true" then + payload["hidden"] = true + end + tunnelRequestDatacenter(payload) +end + +function getConfigInfo() + local payload = {} + payload["api"] = 1106 + payload["appid"] = LuciHttp.formvalue("appId") + payload["key"] = LuciHttp.formvalue("key") + tunnelRequestDatacenter(payload) +end +function connectedDevice() + local payload = {} + payload["api"] = 616 + payload["appid"] = LuciHttp.formvalue("appId") + tunnelRequestDatacenter(payload) +end +function setConfigInfo() + local payload = {} + payload["api"] = 1107 + payload["appid"] = LuciHttp.formvalue("appId") + payload["key"] = LuciHttp.formvalue("key") + payload["value"] = LuciHttp.formvalue("value") + tunnelRequestDatacenter(payload) +end +function enablePlugin() + local payload = {} + payload["api"] = 1108 + payload["appid"] = LuciHttp.formvalue("appId") + payload["status"] = 5 + tunnelRequestDatacenter(payload) +end +function disablePlugin () + local payload = {} + payload["api"] = 1108 + payload["appid"] = LuciHttp.formvalue("appId") + payload["status"] = 6 + tunnelRequestDatacenter(payload) +end +function controlPlugin() + local payload = {} + payload["api"] = 600 + payload["pluginID"] = LuciHttp.formvalue("appId") + payload["info"] = LuciHttp.formvalue("info") + tunnelRequestDatacenter(payload) +end +function deleteDownload() + local payload = {} + payload["api"] = 1110 + payload["appid"] = LuciHttp.formvalue("appId") + payload["idList"] = LuciHttp.formvalue("idList") + payload["deletefile"] = false + if LuciHttp.formvalue("deletefile") == "true" then + payload["deletefile"] = true + end + tunnelRequestDatacenter(payload) +end +function pluginStatus() + local payload = {} + payload["api"] = 1111 + payload["appid"] = LuciHttp.formvalue("appId") + tunnelRequestDatacenter(payload) +end +function pluginDownloadInfo() + local payload = {} + payload["api"] = 1109 + payload["appid"] = LuciHttp.formvalue("appId") + payload["hidden"] = false + if LuciHttp.formvalue("hidden") == "true" then + payload["hidden"] = true + end + payload["lite"] = false + if LuciHttp.formvalue("lite") == "true" then + payload["lite"] = true + end + tunnelRequestDatacenter(payload) +end diff --git a/Mi_Lua/luci/controller/service/index.lua b/Mi_Lua/luci/controller/service/index.lua new file mode 100644 index 0000000..8763c1a --- /dev/null +++ b/Mi_Lua/luci/controller/service/index.lua @@ -0,0 +1,10 @@ +module("luci.controller.service.index", package.seeall) +function index() + local page = node("service") + page.target = firstchild() + page.title = _("") + page.order = nil + page.sysauth = "admin" + page.sysauth_authenticator = "jsonauth" + page.index = true +end diff --git a/Mi_Lua/luci/controller/web/index.lua b/Mi_Lua/luci/controller/web/index.lua new file mode 100644 index 0000000..c53e96a --- /dev/null +++ b/Mi_Lua/luci/controller/web/index.lua @@ -0,0 +1,96 @@ +module("luci.controller.web.index", package.seeall) + +function index() + local root = node() + if not root.target then + root.target = alias("web") + root.index = true + end + local page = node("web") + page.target = firstchild() + page.title = _("") + page.order = 10 + page.sysauth = "admin" + page.mediaurlbase = "/xiaoqiang/web" + page.sysauth_authenticator = "htmlauth" + page.index = true + entry({"web"}, template("web/index"), _("路由器状态"), 10, 0x08) + entry({"web", "home"}, template("web/index"), _("路由器状态"), 70, 0x08) + entry({"web", "manager"}, template("web/manager"), _("终端管理"), 71) + --entry({"web", "plugin"}, template("web/plugin"), _("插件管理"), 72) + --entry({"web", "plugin", "kuaipan"}, template("web/plugins/kuaipan"), _("插件管理_快盘"), 72) + --entry({"web", "plugin", "guest"}, template("web/plugins/guest"), _("插件管理_访客"), 72) + entry({"web", "logout"}, call("action_logout"), 11, 0x09) + entry({"web", "init"}, template("web/init/hello"), _("初始化引导"), 190, 0x09) + entry({"web", "init", "hello"}, template("web/init/hello"), _("欢迎界面"), 198, 0x09) --不需要登录 + entry({"web", "init", "agreement"}, template("web/init/agreement"), _("用户协议"), 198, 0x09) --不需要登录 + entry({"web", "init", "guide"}, template("web/init/guide"), _("引导模式"), 190, 0x08) + + entry({"web", "netset"}, template("web/netset"), _("路由设置"), 73) + entry({"web", "sysset"}, template("web/sysset"), _("路由设置"), 73) + entry({"web", "sysset", "passport"}, template("web/setting/passport"), _("路由器权限"), 18) + entry({"web", "sysset", "reboot"}, template("web/setting/reboot"), _("重启路由器"), 73) + entry({"web", "sysset", "reset"}, template("web/setting/reset"), _("恢复出厂设置"), 73) + + entry({"web", "netset", "wifi"}, template("web/setting/wifi_set"), _("WIFI网络设置"), 20) + entry({"web", "netset", "wifi_mini"}, template("web/setting/wifi_set_mini"), _("WIFI网络快捷设置"), 20) + entry({"web", "netset", "wifi_pro"}, template("web/setting/wifi_set_pro"), _("WIFI网络高级设置"), 60) + entry({"web", "netset", "wifi_txpwr"}, template("web/setting/wifi_txpwr"), _("WIFI强度设置"), 60) + entry({"web", "netset", "wifi_filter"}, template("web/setting/wifi_filter"), _("WIFI访问控制"), 60) + + entry({"web", "netset" ,"net_wan"}, template("web/setting/net_wan"), _("网络设置WAN"), 20) + entry({"web", "netset", "net_lan"}, template("web/setting/net_lan"), _("网络设置LAN"), 30) + entry({"web", "netset", "mac"}, template("web/setting/net_setup_mac"), _("mac 设置"), 40) + entry({"web", "netset", "ipmacband"}, template("web/setting/net_ipmacband"), _("mac 设置"), 40) + entry({"web", "sysset", "qos_pro"}, template("web/setting/qos_pro"), _("QoS 设置"), 40) + + + entry({"web", "sysset", "upgrade"}, template("web/setting/upgrade"), _("路由器固件升级"), 198, 0x01) + entry({"web", "sysset", "upgrade_manual"}, template("web/setting/upgrade_manual", _("路由器手动升级"), 200)) + entry({"web", "sysset", "log"}, template("web/setting/log", _("上传日志"), 201)) + --entry({"web", "sysset", "upload_config"}, template("web/setting/upload_config"), _("上传配置信息"), 202) + + --entry({"web", "setting", "sys_psp"}, template("web/setting/sys_psp"), _("管理小米账号"), 73) + entry({"web", "sysset", "sys_status"}, template("web/setting/sys_status"), _("系统状态"), 73) + + entry({"web", "sysset", "diskformat"}, template("web/setting/diskformat"), _("格式化小强盘"), 202) + entry({"web", "sysset", "nginx"}, template("web/setting/nginx"), _("关闭NGINX"), 203) + entry({"web", "sysset", "upnp"}, template("web/setting/upnp"), _("upnp"), 204) + -- entry({"web", "sysset", "lamp"}, template("web/setting/lamp"), _("LAMP Settings"), 204) + entry({"web", "sysset", "qos"}, template("web/setting/qos"), _("应用限速"), 204) + entry({"web", "sysset", "vpn"}, template("web/setting/vpn"), _("VPN"), 204) + + entry({"web", "sysset", "developer"}, template("web/setting/developer"), _("开发者选项"), 205) + entry({"web", "sysset", "dmz"}, template("web/setting/dmz"), _("DMZ"), 205) + entry({"web", "sysset", "ddns"}, template("web/setting/ddns"), _("DDNS"), 204) + entry({"web", "sysset", "nat"}, template("web/setting/nat"), _("端口转发"), 206) + entry({"web", "sysset", "noflushd"}, template("web/setting/noflushd"), _("磁盘休眠"), 207) + --entry({"web", "sysset", "predownload"}, template("web/setting/predownload"), _("预下载"), 208) + + entry({"web", "detecte"}, template("web/netdetection"), _("网络检测"), 74, 0x01) + entry({"web", "detecte_pro"}, template("web/urldetection"), _("网络高级检测"), 75, 0x01) + entry({"web", "xmaccount"}, template("web/xmaccount"), _("小米帐号验证"), 75, 0x01) + -- entry({"web", "safeurl"}, call("action_safeurl"), _(""), 75, 0x09) + + entry({"web", "webinitrdr"}, template("web/init/webinitrdr"), _("劫持页面"), 300, 0x09) --不需要登录 +end +function action_logout() + local dsp = require "luci.dispatcher" + local sauth = require "luci.sauth" + if dsp.context.authsession then + sauth.kill(dsp.context.authsession) + dsp.context.urltoken.stok = nil + end + luci.http.header("Set-Cookie", "sysauth=; path=" .. dsp.build_url()) + luci.http.header("Set-Cookie", "autologin_v2=;expires=-1;path=/;") + luci.http.redirect(luci.dispatcher.build_url()) +end + +function action_safeurl() + + local safeUrl = luci.http.formvalue("safeurl") + require("luci.template") + luci.template.render("web/safeurl", {safeurl=safeUrl}) + +end + diff --git a/Mi_Lua/luci/datacentertunnel.so b/Mi_Lua/luci/datacentertunnel.so new file mode 100644 index 0000000000000000000000000000000000000000..3dc058c3971da02be5dbafc4c23ab890915fc244 GIT binary patch literal 174576 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 peak then + peak = size + end + if tracefile then + tracefile:write( + "[", what, "] ", info.source, ":", (line or "?"), "\t", + (info.namewhat or ""), "\t", + (info.name or ""), "\t", + size, " (", peak, ")\n" + ) + end + end + + debug.sethook(trap, flags) + + return function() + debug.sethook() + tracefile:close() + end +end + diff --git a/Mi_Lua/luci/dispatcher.lua b/Mi_Lua/luci/dispatcher.lua new file mode 100644 index 0000000..c02413d --- /dev/null +++ b/Mi_Lua/luci/dispatcher.lua @@ -0,0 +1,1037 @@ +--[[ +LuCI - Dispatcher + +Description: +The request dispatcher and module dispatcher generators + +FileId: +$Id: dispatcher.lua 9018 2012-08-14 15:31:26Z jow $ + +License: +Copyright 2008 Steven Barth + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +]]-- + +--- LuCI web dispatcher. + +local fs = require "nixio.fs" +local bit = require("bit") +local sys = require "luci.sys" +local init = require "luci.init" +local util = require "luci.util" +local http = require "luci.http" +local nixio = require "nixio", require "nixio.util" + +local XQSecureUtil = require("xiaoqiang.util.XQSecureUtil") + +module("luci.dispatcher", package.seeall) +context = util.threadlocal() +i18n = require "luci.i18n" +_M.fs = fs + +authenticator = {} +-- Index table +local index = nil + +-- Fastindex +local fi + +--- Build the URL relative to the server webroot from given virtual path. +-- @param ... Virtual path +-- @return Relative URL +function build_url(...) + local path = {...} + local url = { http.getenv("SCRIPT_NAME") or "" } + + local k, v + for k, v in pairs(context.urltoken) do + url[#url+1] = "/;" + url[#url+1] = http.urlencode(k) + url[#url+1] = "=" + url[#url+1] = http.urlencode(v) + end + + local p + for _, p in ipairs(path) do + if p:match("^[a-zA-Z0-9_%-%.%%/,;]+$") then + url[#url+1] = "/" + url[#url+1] = p + end + end + + return table.concat(url, "") +end + +--- Check whether a dispatch node shall be visible +-- @param node Dispatch node +-- @return Boolean indicating whether the node should be visible +function node_visible(node) + if node then + return not ( + (not node.title or #node.title == 0) or + (not node.target or node.hidden == true) or + (type(node.target) == "table" and node.target.type == "firstchild" and + (type(node.nodes) ~= "table" or not next(node.nodes))) + ) + end + return false +end + +--- Return a sorted table of visible childs within a given node +-- @param node Dispatch node +-- @return Ordered table of child node names +function node_childs(node) + local rv = { } + if node then + local k, v + for k, v in util.spairs(node.nodes, + function(a, b) + return (node.nodes[a].order or 100) + < (node.nodes[b].order or 100) + end) + do + if node_visible(v) then + rv[#rv+1] = k + end + end + end + return rv +end + +--- Send a 404 error code and render the "error404" template if available. +-- @param message Custom error message (optional) +-- @return false +function error404(message) + luci.http.status(404, "Not Found") + message = message or "Not Found" + + require("luci.template") + if not luci.util.copcall(luci.template.render, "error404") then + luci.http.prepare_content("text/plain") + luci.http.write(message) + end + return false +end + +--- Send a 500 error code and render the "error500" template if available. +-- @param message Custom error message (optional)# +-- @return false +function error500(message) + --luci.util.perror(message) + local logger = require("xiaoqiang.XQLog") + logger.log(3, "Internal Server Error", message) + message = "Internal Server Error" + if not context.template_header_sent then + luci.http.status(500, "Internal Server Error") + luci.http.prepare_content("text/plain") + luci.http.write(message) + else + require("luci.template") + if not luci.util.copcall(luci.template.render, "error500", {message=message}) then + luci.http.prepare_content("text/plain") + luci.http.write(message) + end + end + return false +end + +function empower(lan,wan,admin) + local XQFunction = require("xiaoqiang.common.XQFunction") + local remoteAddr = luci.http.getenv("REMOTE_ADDR") + local mac = XQFunction.macFormat(luci.sys.net.ip4mac(remoteAddr)) + if not XQFunction.isStrNil(mac) then + local XQSysUtil = require("xiaoqiang.util.XQSysUtil") + if not XQSysUtil.setMacFilter(mac,lan,wan,admin) then + local XQLog = require("xiaoqiang.XQLog") + XQLog.log(3,"Empower failed"..mac) + end + end +end + +function getremotemac() + local XQFunction = require("xiaoqiang.common.XQFunction") + local remote_addr = luci.http.getenv("REMOTE_ADDR") or "" + local mac = luci.sys.net.ip4mac(remote_addr) or "" + return XQFunction.macFormat(mac) +end + +-- TODO auth will be found similar +function authenticator.jsonauth(validator, accs, default) + local XQSysUtil = require("xiaoqiang.util.XQSysUtil") + + local user = luci.http.xqformvalue("username") + local pass = luci.http.xqformvalue("password") + local nonce = luci.http.xqformvalue("nonce") + local uuid = luci.http.xqformvalue("uuid") + local token = luci.http.xqformvalue("token") + local isBinded = XQSysUtil.getPassportBindInfo() + + if isBinded and uuid and token and (uuid == isBinded) then + local XQDBUtil = require("xiaoqiang.util.XQDBUtil") + local passport = XQDBUtil.fetchPassport(uuid)[1] + if passport and token == passport.token then + empower("1","1",nil) + local logtype = "1" + luci.http.header("Set-Cookie", "psp=" .. uuid .. "|||" .. logtype .. "|||" .. token .. ";path=/;") + return default, logtype + end + end + + if nonce then + if XQSecureUtil.checkNonce(nonce, getremotemac()) then + if XQSecureUtil.checkUser(user, nonce, pass) then + empower("1","1",nil) + local logtype = "2" + luci.http.header("Set-Cookie", "psp=" .. user .. "|||" .. logtype .. "|||0;path=/;") + return user, logtype + end + else + context.path = {} + luci.http.write([[{"code":1582,"msg":"nonce invalid"}]]) + return false + end + else + if XQSecureUtil.checkPlaintextPwd(user, pass) then + empower("1","1",nil) + local logtype = "2" + luci.http.header("Set-Cookie", "psp=" .. user .. "|||" .. logtype .. "|||0;path=/;") + return user, logtype + else + context.path = {} + luci.http.write([[{"code":401,"msg":"密码错误"}]]) + return false + end + end + context.path = {} + luci.http.write([[{"code":401,"msg":"not auth"}]]) + return false +end + +function authenticator.htmlauth(validator, accs, default) + local XQSysUtil = require("xiaoqiang.util.XQSysUtil") + local redirectKey = luci.http.xqformvalue("redirectKey") + local isBinded = XQSysUtil.getPassportBindInfo() + + if redirectKey then + local check = XQSecureUtil.checkRedirectKey(redirectKey) + if check then + if check == "1" and isBinded then + local XQDBUtil = require("xiaoqiang.util.XQDBUtil") + local bindUUID = XQSysUtil.getBindUUID() + local passport = XQDBUtil.fetchPassport(bindUUID)[1] + if passport then + luci.http.header("Set-Cookie", "psp=" .. bindUUID .. "|||1|||" .. passport.token .. ";path=/;") + return default, check + end + elseif check == "2" then + luci.http.header("Set-Cookie", "psp=admin|||2|||0;path=/;") + return "admin", check + end + end + end + require("luci.i18n") + require("luci.template") + context.path = {} + luci.template.render("web/sysauth", {duser=default, fuser=user}) + return false +end + +function authenticator.htmlauth_moblie(validator, accs, default) + local user = luci.http.xqformvalue("username") + local pass = luci.http.xqformvalue("password") + local nonce = luci.http.xqformvalue("nonce") + if nonce then + if XQSecureUtil.checkNonce(nonce, getremotemac()) and XQSecureUtil.checkUser(user, nonce, pass) then + empower("1","1",nil) + return user, "2" + end + end + require("luci.i18n") + require("luci.template") + context.path = {} + luci.template.render("mobile/sysauth", {duser=default, fuser=user}) + return false +end + +function check_show_syslock(sysauth) + local XQFunction = require("xiaoqiang.common.XQFunction") + local XQSysUtil = require("xiaoqiang.util.XQSysUtil") + + if XQFunction.sysLockStatus() == 1 then + if XQSysUtil.isUpgrading() then + require("luci.i18n") + require("luci.template") + if type(sysauth) == "string" and sysauth == "htmlauth" then + context.path = {} + luci.template.render("web/syslock",{}) + elseif type(sysauth) == "string" and sysauth == "jsonauth" then + context.path = {} + luci.http.write([[{"code":403,"msg":"system locked"}]]) + else + XQFunction.sysUnlock() + return false + end + return true + else + XQFunction.sysUnlock() + end + end + return false +end + +function http_request_log(request, tag) + local XQLog = require("xiaoqiang.XQLog") + local requestUri = request:getenv("REQUEST_URI") + if requestUri and tag and type(tag) == "string" then + local uriInfo = luci.util.split(requestUri,"?") + XQLog.log(6,tag..":"..uriInfo[1]) + if uriInfo[2] then + XQLog.log(7,uriInfo[2]) + end + end +end + +-- API Permissions +function _noauthAccessAllowed(flag) + if flag == nil then + return false + end + if bit.band(flag, 0x01) == 0x01 then + return true + else + return false + end +end + +function _remoteAccessForbidden(flag) + if flag == nil then + return false + end + if bit.band(flag, 0x02) == 0x02 then + return true + else + return false + end +end + +function _syslockAccessAllowed(flag) + if flag == nil then + return false + end + if bit.band(flag, 0x04) == 0x04 then + return true + else + return false + end +end + +function _noinitAccessAllowed(flag) + local xqsys = require("xiaoqiang.util.XQSysUtil") + if xqsys.getInitInfo() then + return true + else + if flag == nil then + return false + end + if bit.band(flag, 0x08) == 0x08 then + return true + else + return false + end + end +end + +function _sdkFilter(flag) + if flag == nil then + return false + end + if bit.band(flag, 0x10) == 0x10 then + return true + else + return false + end +end + +--- Dispatch an HTTP request. +-- @param request LuCI HTTP Request object +function httpdispatch(request, prefix) + http_request_log(request, "request") + -- 设置全局表,使 _() 使用当前文件内的 _函数,即为标记tag, translate 可以直接使用翻译 + _G._ = _ + _G.translate = i18n.translate + luci.http.context.request = request + + local r = {} + context.request = r + context.urltoken = {} + + local pathinfo = http.urldecode(request:getenv("PATH_INFO") or "", true) + + if prefix then + for _, node in ipairs(prefix) do + r[#r+1] = node + end + end + + local tokensok = true + for node in pathinfo:gmatch("[^/]+") do + local tkey, tval + if tokensok then + tkey, tval = node:match(";(%w+)=([a-fA-F0-9]*)") + end + if tkey then + context.urltoken[tkey] = tval + else + tokensok = false + r[#r+1] = node + end + end + + local stat, err = util.coxpcall(function() + dispatch(context.request) + end, error500) + luci.http.close() + http_request_log(request, "finished") +end + +--- Dispatches a LuCI virtual path. +-- @param request Virtual path +function dispatch(request) + local ctx = context + ctx.path = request + + local conf = require "luci.config" + assert(conf.main, + "/etc/config/luci seems to be corrupt, unable to find section 'main'") + + local lang = conf.main.lang or "auto" + if lang == "auto" then + local aclang = http.getenv("HTTP_ACCEPT_LANGUAGE") or "" + for lpat in aclang:gmatch("[%w-]+") do + lpat = lpat and lpat:gsub("-", "_") + if conf.languages[lpat] then + lang = lpat + break + end + end + end + require "luci.i18n".setlanguage(lang) + + local c = ctx.tree + local stat + if not c then + c = createtree() + end + + local track = {} + local args = {} + ctx.args = args + ctx.requestargs = ctx.requestargs or args + local n + local token = ctx.urltoken + local preq = {} + local freq = {} + + for i, s in ipairs(request) do + preq[#preq+1] = s + freq[#freq+1] = s + c = c.nodes[s] + n = i + if not c then + break + end + + util.update(track, c) + + if c.leaf then + break + end + end + + if c and c.leaf then + for j=n+1, #request do + args[#args+1] = request[j] + freq[#freq+1] = request[j] + end + end + + ctx.requestpath = ctx.requestpath or freq + ctx.path = preq + + if track.i18n then + i18n.loadc(track.i18n) + end + + -- Init template engine + if (c and c.index) or not track.notemplate then + local tpl = require("luci.template") + local media = track.mediaurlbase or luci.config.main.mediaurlbase + if not pcall(tpl.Template, "themes/%s/header" % fs.basename(media)) then + media = nil + for name, theme in pairs(luci.config.themes) do + if name:sub(1,1) ~= "." and pcall(tpl.Template, + "themes/%s/header" % fs.basename(theme)) then + media = theme + end + end + assert(media, "No valid theme found") + end + + local function _ifattr(cond, key, val) + if cond then + local env = getfenv(3) + local scope = (type(env.self) == "table") and env.self + return string.format( + ' %s="%s"', tostring(key), + luci.util.pcdata(tostring( val + or (type(env[key]) ~= "function" and env[key]) + or (scope and type(scope[key]) ~= "function" and scope[key]) + or "" )) + ) + else + return '' + end + end + tpl.context.viewns = setmetatable({ + write = luci.http.write; + include = function(name) tpl.Template(name):render(getfenv(2)) end; + translate = i18n.translate; + export = function(k, v) if tpl.context.viewns[k] == nil then tpl.context.viewns[k] = v end end; + striptags = util.striptags; + pcdata = util.pcdata; + media = media; + theme = fs.basename(media); + resource = luci.config.main.resourcebase; + ifattr = function(...) return _ifattr(...) end; + attr = function(...) return _ifattr(true, ...) end; + }, {__index=function(table, key) + if key == "controller" then + return build_url() + elseif key == "REQUEST_URI" then + return build_url(unpack(ctx.requestpath)) + else + return rawget(table, key) or _G[key] + end + end}) + end + + track.dependent = (track.dependent ~= false) + assert(not track.dependent or not track.auto, + "Access Violation\nThe page at '" .. table.concat(request, "/") .. "/' " .. + "has no parent node so the access to this location has been denied.\n" .. + "This is a software bug, please report this message at " .. + "http://luci.subsignal.org/trac/newticket" + ) + if not _syslockAccessAllowed(track.flag) then + if check_show_syslock(track.sysauth_authenticator) then + return + end + end + if not _noinitAccessAllowed(track.flag) then + luci.http.status(403, "Forbidden") + return + end + local isremote = http.getenv("REMOTE_ADDR") == "127.0.0.1" + if _sdkFilter(track.flag) and not isremote then + local sdkutil = require("xiaoqiang.util.XQSDKUtil") + if not sdkutil.checkPermission(getremotemac()) then + context.path = {} + luci.http.write([[{"code":1500,"msg":"Permission denied"}]]) + return + end + end + if not isremote and not _noauthAccessAllowed(track.flag) and track.sysauth then + local sauth = require "luci.sauth" + local crypto = require "xiaoqiang.util.XQCryptoUtil" + local sysutil = require "xiaoqiang.util.XQSysUtil" + local isBinded = sysutil.getPassportBindInfo() + + local authen = type(track.sysauth_authenticator) == "function" + and track.sysauth_authenticator + or authenticator[track.sysauth_authenticator] + + local def = (type(track.sysauth) == "string") and track.sysauth + local accs = def and {track.sysauth} or track.sysauth + local sess = ctx.urltoken.stok + local sdat = sauth.read(sess) + local user + if sdat then + if ctx.urltoken.stok == sdat.token then + if (sdat.ltype == "2" or (sdat.ltype == "1" and isBinded)) then + user = sdat.user + end + end + else + local eu = http.getenv("HTTP_AUTH_USER") + local ep = http.getenv("HTTP_AUTH_PASS") + if eu and ep and luci.sys.user.checkpasswd(eu, ep) then + authen = function() return eu end + end + end + + if not util.contains(accs, user) then + if authen then + ctx.urltoken.stok = nil + local user, logintype = authen(nil, accs, def) + if not user or not util.contains(accs, user) then + return + else + local sid = sess or luci.sys.uniqueid(16) + local ltype = logintype or "2" + local token = luci.sys.uniqueid(16) + sauth.reap() + sauth.write(token, { + user=user, + token=token, + ltype=ltype, + secret=luci.sys.uniqueid(16) + }) + ctx.urltoken.stok = token + ctx.authsession = token + ctx.authuser = user + end + else + luci.http.status(403, "Forbidden") + return + end + else + ctx.authsession = sess + ctx.authuser = user + end + end + + if track.setgroup then + luci.sys.process.setgroup(track.setgroup) + end + if track.setuser then + luci.sys.process.setuser(track.setuser) + end + + local target = nil + if c then + if type(c.target) == "function" then + target = c.target + elseif type(c.target) == "table" then + target = c.target.target + end + end + + if c and (c.index or type(target) == "function") then + ctx.dispatched = c + ctx.requested = ctx.requested or ctx.dispatched + end + + if c and c.index then + local tpl = require "luci.template" + + if util.copcall(tpl.render, "indexer", {}) then + return true + end + end + + if type(target) == "function" then + util.copcall(function() + local oldenv = getfenv(target) + local module = require(c.module) + local env = setmetatable({}, {__index= + + function(tbl, key) + return rawget(tbl, key) or module[key] or oldenv[key] + end}) + setfenv(target, env) + end) + + local ok, err + if type(c.target) == "table" then + ok, err = util.copcall(target, c.target, unpack(args)) + else + ok, err = util.copcall(target, unpack(args)) + end + assert(ok, + "Failed to execute " .. (type(c.target) == "function" and "function" or c.target.type or "unknown") .. + " dispatcher target for entry '/" .. table.concat(request, "/") .. "'.\n" .. + "The called action terminated with an exception:\n" .. tostring(err or "(unknown)")) + else + local root = node() + if not root or not root.target then + error404("No root node was registered, this usually happens if no module was installed.\n" .. + "Install luci-mod-admin-full and retry. " .. + "If the module is already installed, try removing the /tmp/luci-indexcache file.") + else + error404("No page is registered at '/" .. table.concat(request, "/") .. "'.\n" .. + "If this url belongs to an extension, make sure it is properly installed.\n" .. + "If the extension was recently installed, try removing the /tmp/luci-indexcache file.") + end + end +end + +--- Generate the dispatching index using the best possible strategy. +function createindex() + local path = luci.util.libpath() .. "/controller/" + local suff = { ".lua", ".lua.gz" } + +-- if luci.util.copcall(require, "luci.fastindex") then +-- createindex_fastindex(path, suff) +-- else + createindex_plain(path, suff) +-- end +end + +--- Generate the dispatching index using the fastindex C-indexer. +-- @param path Controller base directory +-- @param suffixes Controller file suffixes +function createindex_fastindex(path, suffixes) + index = {} + + if not fi then + fi = luci.fastindex.new("index") + for _, suffix in ipairs(suffixes) do + fi.add(path .. "*" .. suffix) + fi.add(path .. "*/*" .. suffix) + end + end + fi.scan() + + for k, v in pairs(fi.indexes) do + index[v[2]] = v[1] + end +end + +--- Generate the dispatching index using the native file-cache based strategy. +-- @param path Controller base directory +-- @param suffixes Controller file suffixes +function createindex_plain(path, suffixes) + local controllers = { } + for _, suffix in ipairs(suffixes) do + nixio.util.consume((fs.glob(path .. "*" .. suffix)), controllers) + nixio.util.consume((fs.glob(path .. "*/*" .. suffix)), controllers) + end + + if indexcache then + local cachedate = fs.stat(indexcache, "mtime") + if cachedate then + local realdate = 0 + for _, obj in ipairs(controllers) do + local omtime = fs.stat(obj, "mtime") + realdate = (omtime and omtime > realdate) and omtime or realdate + end + + if cachedate > realdate then + assert( + sys.process.info("uid") == fs.stat(indexcache, "uid") + and fs.stat(indexcache, "modestr") == "rw-------", + "Fatal: Indexcache is not sane!" + ) + + index = loadfile(indexcache)() + return index + end + end + end + + index = {} + + for i,c in ipairs(controllers) do + local modname = "luci.controller." .. c:sub(#path+1, #c):gsub("/", ".") + for _, suffix in ipairs(suffixes) do + modname = modname:gsub(suffix.."$", "") + end + + local mod = require(modname) + assert(mod ~= true, + "Invalid controller file found\n" .. + "The file '" .. c .. "' contains an invalid module line.\n" .. + "Please verify whether the module name is set to '" .. modname .. + "' - It must correspond to the file path!") + + local idx = mod.index + assert(type(idx) == "function", + "Invalid controller file found\n" .. + "The file '" .. c .. "' contains no index() function.\n" .. + "Please make sure that the controller contains a valid " .. + "index function and verify the spelling!") + + index[modname] = idx + end + + if indexcache then + local f = nixio.open(indexcache, "w", 600) + f:writeall(util.get_bytecode(index)) + f:close() + end +end + +-- Create the dispatching tree from the index. +-- Build the index before if it does not exist yet. +function createtree() + if not index then + createindex() + end + + local ctx = context + local tree = {nodes={}, inreq=true} + local modi = {} + + ctx.treecache = setmetatable({}, {__mode="v"}) + ctx.tree = tree + ctx.modifiers = modi + + -- Load default translation + require "luci.i18n".loadc("base") + + local scope = setmetatable({}, {__index = luci.dispatcher}) + + for k, v in pairs(index) do + scope._NAME = k + setfenv(v, scope) + v() + end + + local function modisort(a,b) + return modi[a].order < modi[b].order + end + + for _, v in util.spairs(modi, modisort) do + scope._NAME = v.module + setfenv(v.func, scope) + v.func() + end + + return tree +end + +--- Register a tree modifier. +-- @param func Modifier function +-- @param order Modifier order value (optional) +function modifier(func, order) + context.modifiers[#context.modifiers+1] = { + func = func, + order = order or 0, + module + = getfenv(2)._NAME + } +end + +--- Clone a node of the dispatching tree to another position. +-- @param path Virtual path destination +-- @param clone Virtual path source +-- @param title Destination node title (optional) +-- @param order Destination node order value (optional) +-- @param flag For extension (optional) +-- @return Dispatching tree node +function assign(path, clone, title, order, flag) + local obj = node(unpack(path)) + obj.nodes = nil + obj.module = nil + + obj.title = title + obj.order = order + obj.flag = flag + + setmetatable(obj, {__index = _create_node(clone)}) + return obj +end + +--- Create a new dispatching node and define common parameters. +-- @param path Virtual path +-- @param target Target function to call when dispatched. +-- @param title Destination node title +-- @param order Destination node order value (optional) +-- @param flag For extension (optional) +-- @return Dispatching tree node +function entry(path, target, title, order, flag) + local c = node(unpack(path)) + + c.target = target + c.title = title + c.order = order + c.flag = flag + c.module = getfenv(2)._NAME + + return c +end + +--- Fetch or create a dispatching node without setting the target module or +-- enabling the node. +-- @param ... Virtual path +-- @return Dispatching tree node +function get(...) + return _create_node({...}) +end + +--- Fetch or create a new dispatching node. +-- @param ... Virtual path +-- @return Dispatching tree node +function node(...) + local c = _create_node({...}) + + c.module = getfenv(2)._NAME + c.auto = nil + + return c +end + +function _create_node(path) + if #path == 0 then + return context.tree + end + + local name = table.concat(path, ".") + local c = context.treecache[name] + + if not c then + local last = table.remove(path) + local parent = _create_node(path) + + c = {nodes={}, auto=true} + -- the node is "in request" if the request path matches + -- at least up to the length of the node path + if parent.inreq and context.path[#path+1] == last then + c.inreq = true + end + parent.nodes[last] = c + context.treecache[name] = c + end + return c +end + +-- Subdispatchers -- + +function _firstchild() + local path = { unpack(context.path) } + local name = table.concat(path, ".") + local node = context.treecache[name] + + local lowest + if node and node.nodes and next(node.nodes) then + local k, v + for k, v in pairs(node.nodes) do + if not lowest or + (v.order or 100) < (node.nodes[lowest].order or 100) + then + lowest = k + end + end + end + + assert(lowest ~= nil, + "The requested node contains no childs, unable to redispatch") + + path[#path+1] = lowest + dispatch(path) +end + +--- Alias the first (lowest order) page automatically +function firstchild() + return { type = "firstchild", target = _firstchild } +end + +--- Create a redirect to another dispatching node. +-- @param ... Virtual path destination +function alias(...) + local req = {...} + return function(...) + for _, r in ipairs({...}) do + req[#req+1] = r + end + + dispatch(req) + end +end + +--- Rewrite the first x path values of the request. +-- @param n Number of path values to replace +-- @param ... Virtual path to replace removed path values with +function rewrite(n, ...) + local req = {...} + return function(...) + local dispatched = util.clone(context.dispatched) + + for i=1,n do + table.remove(dispatched, 1) + end + + for i, r in ipairs(req) do + table.insert(dispatched, i, r) + end + + for _, r in ipairs({...}) do + dispatched[#dispatched+1] = r + end + + dispatch(dispatched) + end +end + +local function _call(self, ...) + local func = getfenv()[self.name] + assert(func ~= nil, + 'Cannot resolve function "' .. self.name .. '". Is it misspelled or local?') + + assert(type(func) == "function", + 'The symbol "' .. self.name .. '" does not refer to a function but data ' .. + 'of type "' .. type(func) .. '".') + + if #self.argv > 0 then + return func(unpack(self.argv), ...) + else + return func(...) + end +end + +--- Create a function-call dispatching target. +-- @param name Target function of local controller +-- @param ... Additional parameters passed to the function +function call(name, ...) + return {type = "call", argv = {...}, name = name, target = _call} +end + + +local _template = function(self, ...) + require "luci.template".render(self.view) +end + +--- Create a template render dispatching target. +-- @param name Template to be rendered +function template(name) + return {type = "template", view = name, target = _template} +end + +local function _arcombine(self, ...) + local argv = {...} + local target = #argv > 0 and self.targets[2] or self.targets[1] + setfenv(target.target, self.env) + target:target(unpack(argv)) +end + +--- Create a combined dispatching target for non argv and argv requests. +-- @param trg1 Overview Target +-- @param trg2 Detail Target +function arcombine(trg1, trg2) + return {type = "arcombine", env = getfenv(), target = _arcombine, targets = {trg1, trg2}} +end + +--- Access the luci.i18n translate() api. +-- @class function +-- @name translate +-- @param text Text to translate +translate = i18n.translate + +--- No-op function used to mark translation entries for menu labels. +-- This function does not actually translate the given argument but +-- is used by build/i18n-scan.pl to find translatable entries. +function _(text) + return text +end \ No newline at end of file diff --git a/Mi_Lua/luci/fs.lua b/Mi_Lua/luci/fs.lua new file mode 100644 index 0000000..53b2ee8 --- /dev/null +++ b/Mi_Lua/luci/fs.lua @@ -0,0 +1,244 @@ +--[[ +LuCI - Filesystem tools + +Description: +A module offering often needed filesystem manipulation functions + +FileId: +$Id: fs.lua 5134 2009-07-24 17:34:40Z Cyrus $ + +License: +Copyright 2008 Steven Barth + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +]]-- + +local io = require "io" +local os = require "os" +local ltn12 = require "luci.ltn12" +local fs = require "nixio.fs" +local nutil = require "nixio.util" + +local type = type + +--- LuCI filesystem library. +module "luci.fs" + +--- Test for file access permission on given path. +-- @class function +-- @name access +-- @param str String value containing the path +-- @return Number containing the return code, 0 on sucess or nil on error +-- @return String containing the error description (if any) +-- @return Number containing the os specific errno (if any) +access = fs.access + +--- Evaluate given shell glob pattern and return a table containing all matching +-- file and directory entries. +-- @class function +-- @name glob +-- @param filename String containing the path of the file to read +-- @return Table containing file and directory entries or nil if no matches +-- @return String containing the error description (if no matches) +-- @return Number containing the os specific errno (if no matches) +function glob(...) + local iter, code, msg = fs.glob(...) + if iter then + return nutil.consume(iter) + else + return nil, code, msg + end +end + +--- Checks wheather the given path exists and points to a regular file. +-- @param filename String containing the path of the file to test +-- @return Boolean indicating wheather given path points to regular file +function isfile(filename) + return fs.stat(filename, "type") == "reg" +end + +--- Checks wheather the given path exists and points to a directory. +-- @param dirname String containing the path of the directory to test +-- @return Boolean indicating wheather given path points to directory +function isdirectory(dirname) + return fs.stat(dirname, "type") == "dir" +end + +--- Read the whole content of the given file into memory. +-- @param filename String containing the path of the file to read +-- @return String containing the file contents or nil on error +-- @return String containing the error message on error +readfile = fs.readfile + +--- Write the contents of given string to given file. +-- @param filename String containing the path of the file to read +-- @param data String containing the data to write +-- @return Boolean containing true on success or nil on error +-- @return String containing the error message on error +writefile = fs.writefile + +--- Copies a file. +-- @param source Source file +-- @param dest Destination +-- @return Boolean containing true on success or nil on error +copy = fs.datacopy + +--- Renames a file. +-- @param source Source file +-- @param dest Destination +-- @return Boolean containing true on success or nil on error +rename = fs.move + +--- Get the last modification time of given file path in Unix epoch format. +-- @param path String containing the path of the file or directory to read +-- @return Number containing the epoch time or nil on error +-- @return String containing the error description (if any) +-- @return Number containing the os specific errno (if any) +function mtime(path) + return fs.stat(path, "mtime") +end + +--- Set the last modification time of given file path in Unix epoch format. +-- @param path String containing the path of the file or directory to read +-- @param mtime Last modification timestamp +-- @param atime Last accessed timestamp +-- @return 0 in case of success nil on error +-- @return String containing the error description (if any) +-- @return Number containing the os specific errno (if any) +function utime(path, mtime, atime) + return fs.utimes(path, atime, mtime) +end + +--- Return the last element - usually the filename - from the given path with +-- the directory component stripped. +-- @class function +-- @name basename +-- @param path String containing the path to strip +-- @return String containing the base name of given path +-- @see dirname +basename = fs.basename + +--- Return the directory component of the given path with the last element +-- stripped of. +-- @class function +-- @name dirname +-- @param path String containing the path to strip +-- @return String containing the directory component of given path +-- @see basename +dirname = fs.dirname + +--- Return a table containing all entries of the specified directory. +-- @class function +-- @name dir +-- @param path String containing the path of the directory to scan +-- @return Table containing file and directory entries or nil on error +-- @return String containing the error description on error +-- @return Number containing the os specific errno on error +function dir(...) + local iter, code, msg = fs.dir(...) + if iter then + local t = nutil.consume(iter) + t[#t+1] = "." + t[#t+1] = ".." + return t + else + return nil, code, msg + end +end + +--- Create a new directory, recursively on demand. +-- @param path String with the name or path of the directory to create +-- @param recursive Create multiple directory levels (optional, default is true) +-- @return Number with the return code, 0 on sucess or nil on error +-- @return String containing the error description on error +-- @return Number containing the os specific errno on error +function mkdir(path, recursive) + return recursive and fs.mkdirr(path) or fs.mkdir(path) +end + +--- Remove the given empty directory. +-- @class function +-- @name rmdir +-- @param path String containing the path of the directory to remove +-- @return Number with the return code, 0 on sucess or nil on error +-- @return String containing the error description on error +-- @return Number containing the os specific errno on error +rmdir = fs.rmdir + +local stat_tr = { + reg = "regular", + dir = "directory", + lnk = "link", + chr = "character device", + blk = "block device", + fifo = "fifo", + sock = "socket" +} +--- Get information about given file or directory. +-- @class function +-- @name stat +-- @param path String containing the path of the directory to query +-- @return Table containing file or directory properties or nil on error +-- @return String containing the error description on error +-- @return Number containing the os specific errno on error +function stat(path, key) + local data, code, msg = fs.stat(path) + if data then + data.mode = data.modestr + data.type = stat_tr[data.type] or "?" + end + return key and data and data[key] or data, code, msg +end + +--- Set permissions on given file or directory. +-- @class function +-- @name chmod +-- @param path String containing the path of the directory +-- @param perm String containing the permissions to set ([ugoa][+-][rwx]) +-- @return Number with the return code, 0 on sucess or nil on error +-- @return String containing the error description on error +-- @return Number containing the os specific errno on error +chmod = fs.chmod + +--- Create a hard- or symlink from given file (or directory) to specified target +-- file (or directory) path. +-- @class function +-- @name link +-- @param path1 String containing the source path to link +-- @param path2 String containing the destination path for the link +-- @param symlink Boolean indicating wheather to create a symlink (optional) +-- @return Number with the return code, 0 on sucess or nil on error +-- @return String containing the error description on error +-- @return Number containing the os specific errno on error +function link(src, dest, sym) + return sym and fs.symlink(src, dest) or fs.link(src, dest) +end + +--- Remove the given file. +-- @class function +-- @name unlink +-- @param path String containing the path of the file to remove +-- @return Number with the return code, 0 on sucess or nil on error +-- @return String containing the error description on error +-- @return Number containing the os specific errno on error +unlink = fs.unlink + +--- Retrieve target of given symlink. +-- @class function +-- @name readlink +-- @param path String containing the path of the symlink to read +-- @return String containing the link target or nil on error +-- @return String containing the error description on error +-- @return Number containing the os specific errno on error +readlink = fs.readlink diff --git a/Mi_Lua/luci/http.lua b/Mi_Lua/luci/http.lua new file mode 100644 index 0000000..ed8fbc3 --- /dev/null +++ b/Mi_Lua/luci/http.lua @@ -0,0 +1,336 @@ +--[[ +LuCI - HTTP-Interaction + +Description: +HTTP-Header manipulator and form variable preprocessor + +License: +Copyright 2008 Steven Barth + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +]]-- + +local ltn12 = require "luci.ltn12" +local protocol = require "luci.http.protocol" +local util = require "luci.util" +local string = require "string" +local coroutine = require "coroutine" +local table = require "table" + +local ipairs, pairs, next, type, tostring, error = + ipairs, pairs, next, type, tostring, error + +--- LuCI Web Framework high-level HTTP functions. +module ("luci.http", package.seeall) + +context = util.threadlocal() + +Request = util.class() +function Request.__init__(self, env, sourcein, sinkerr) + self.input = sourcein + self.error = sinkerr + + + -- File handler + self.filehandler = function() end + + -- HTTP-Message table + self.message = { + env = env, + headers = {}, + params = protocol.urldecode_params(env.QUERY_STRING or ""), + } + + self.parsed_input = false +end + +function Request.formvalue(self, name, noparse) + if not noparse and not self.parsed_input then + self:_parse_input() + end + + if name then + return self.message.params[name] + else + return self.message.params + end +end + +function Request.formvaluetable(self, prefix) + local vals = {} + prefix = prefix and prefix .. "." or "." + + if not self.parsed_input then + self:_parse_input() + end + + local void = self.message.params[nil] + for k, v in pairs(self.message.params) do + if k:find(prefix, 1, true) == 1 then + vals[k:sub(#prefix + 1)] = tostring(v) + end + end + + return vals +end + +function Request.content(self) + if not self.parsed_input then + self:_parse_input() + end + + return self.message.content, self.message.content_length +end + +function Request.getcookie(self, name) + local c = string.gsub(";" .. (self:getenv("HTTP_COOKIE") or "") .. ";", "%s*;%s*", ";") + local p = ";" .. name .. "=(.-);" + local i, j, value = c:find(p) + return value and urldecode(value) +end + +function Request.getenv(self, name) + if name then + return self.message.env[name] + else + return self.message.env + end +end + +function Request.setfilehandler(self, callback) + self.filehandler = callback +end + +function Request._parse_input(self) + protocol.parse_message_body( + self.input, + self.message, + self.filehandler + ) + self.parsed_input = true +end + +--- Close the HTTP-Connection. +function close() + if not context.eoh then + context.eoh = true + coroutine.yield(3) + end + + if not context.closed then + context.closed = true + coroutine.yield(5) + end +end + +--- Return the request content if the request was of unknown type. +-- @return HTTP request body +-- @return HTTP request body length +function content() + return context.request:content() +end + +--- Get a certain HTTP input value or a table of all input values. +-- @param name Name of the GET or POST variable to fetch +-- @param noparse Don't parse POST data before getting the value +-- @return HTTP input value or table of all input value +function formvalue(name, noparse) + return context.request:formvalue(name, noparse) +end + +function xqformvalue(name, noparse) + local XQSecureUtil = require("xiaoqiang.util.XQSecureUtil") + local value = context.request:formvalue(name, noparse) + return XQSecureUtil.xssCheck(value) +end + +--- Get a table of all HTTP input values with a certain prefix. +-- @param prefix Prefix +-- @return Table of all HTTP input values with given prefix +function formvaluetable(prefix) + return context.request:formvaluetable(prefix) +end + +--- Get the value of a certain HTTP-Cookie. +-- @param name Cookie Name +-- @return String containing cookie data +function getcookie(name) + return context.request:getcookie(name) +end + +--- Get the value of a certain HTTP environment variable +-- or the environment table itself. +-- @param name Environment variable +-- @return HTTP environment value or environment table +function getenv(name) + return context.request:getenv(name) +end + +--- Set a handler function for incoming user file uploads. +-- @param callback Handler function +function setfilehandler(callback) + return context.request:setfilehandler(callback) +end + +--- Send a HTTP-Header. +-- @param key Header key +-- @param value Header value +function header(key, value) + if not context.headers then + context.headers = {} + end + context.headers[key:lower()] = value + coroutine.yield(2, key, value) +end + +--- Set the mime type of following content data. +-- @param mime Mimetype of following content +function prepare_content(mime) + if not context.headers or not context.headers["content-type"] then + if mime == "application/xhtml+xml" then + if not getenv("HTTP_ACCEPT") or + not getenv("HTTP_ACCEPT"):find("application/xhtml+xml", nil, true) then + mime = "text/html; charset=UTF-8" + end + header("Vary", "Accept") + end + header("Content-Type", mime) + end +end + +--- Get the RAW HTTP input source +-- @return HTTP LTN12 source +function source() + return context.request.input +end + +--- Set the HTTP status code and status message. +-- @param code Status code +-- @param message Status message +function status(code, message) + code = code or 200 + message = message or "OK" + context.status = code + coroutine.yield(1, code, message) +end + +--- Send a chunk of content data to the client. +-- This function is as a valid LTN12 sink. +-- If the content chunk is nil this function will automatically invoke close. +-- @param content Content chunk +-- @param src_err Error object from source (optional) +-- @see close +function write(content, src_err) + if not content then + if src_err then + error(src_err) + else + close() + end + return true + elseif #content == 0 then + return true + else + if not context.eoh then + if not context.status then + status() + end + if not context.headers or not context.headers["content-type"] then + header("Content-Type", "text/html; charset=utf-8") + end + if not context.headers["cache-control"] then + header("Cache-Control", "no-cache") + header("Expires", "0") + end + + context.eoh = true + coroutine.yield(3) + end + coroutine.yield(4, content) + return true + end +end + +--- Splice data from a filedescriptor to the client. +-- @param fp File descriptor +-- @param size Bytes to splice (optional) +function splice(fd, size) + coroutine.yield(6, fd, size) +end + +--- Redirects the client to a new URL and closes the connection. +-- @param url Target URL +function redirect(url) + status(302, "Found") + header("Location", url) + close() +end + +--- Create a querystring out of a table of key - value pairs. +-- @param table Query string source table +-- @return Encoded HTTP query string +function build_querystring(q) + local s = { "?" } + + for k, v in pairs(q) do + if #s > 1 then s[#s+1] = "&" end + + s[#s+1] = urldecode(k) + s[#s+1] = "=" + s[#s+1] = urldecode(v) + end + + return table.concat(s, "") +end + +--- Return the URL-decoded equivalent of a string. +-- @param str URL-encoded string +-- @param no_plus Don't decode + to " " +-- @return URL-decoded string +-- @see urlencode +urldecode = protocol.urldecode + +--- Return the URL-encoded equivalent of a string. +-- @param str Source string +-- @return URL-encoded string +-- @see urldecode +urlencode = protocol.urlencode + +function writeJsonNoLog(x) + if x == nil then + write("null") + elseif type(x) == "table" then + local json = require("luci.json") + write(json.encode(x)) + elseif type(x) == "number" or type(x) == "boolean" then + if (x ~= x) then + -- NaN is the only value that doesn't equal to itself. + write("Number.NaN") + else + write(tostring(x)) + end + else + write('"%s"' % tostring(x):gsub('["%z\1-\31]', function(c) + return '\\u%04x' % c:byte(1) + end)) + end +end + +--- Send the given data as JSON encoded string. +-- @param data Data to send +function write_json(x) + local XQLog = require("xiaoqiang.XQLog") + XQLog.log(7,x) + writeJsonNoLog(x) +end diff --git a/Mi_Lua/luci/http/protocol.lua b/Mi_Lua/luci/http/protocol.lua new file mode 100644 index 0000000..00a5f71 --- /dev/null +++ b/Mi_Lua/luci/http/protocol.lua @@ -0,0 +1,727 @@ +--[[ + +HTTP protocol implementation for LuCI +(c) 2008 Freifunk Leipzig / Jo-Philipp Wich + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +$Id: protocol.lua 9195 2012-08-29 13:06:58Z jow $ + +]]-- + +--- LuCI http protocol class. +-- This class contains several functions useful for http message- and content +-- decoding and to retrive form data from raw http messages. +module("luci.http.protocol", package.seeall) + +local util = require "luci.util" +local ltn12 = require("luci.ltn12") + +HTTP_MAX_CONTENT = 1024*32 -- 8 kB maximum content size + +--- Decode an urlencoded string - optionally without decoding +-- the "+" sign to " " - and return the decoded string. +-- @param str Input string in x-www-urlencoded format +-- @param no_plus Don't decode "+" signs to spaces +-- @return The decoded string +-- @see urlencode +function urldecode( str, no_plus ) + + local function __chrdec( hex ) + return string.char( tonumber( hex, 16 ) ) + end + + if type(str) == "string" then + if not no_plus then + str = str:gsub( "+", " " ) + end + + str = str:gsub( "%%([a-fA-F0-9][a-fA-F0-9])", __chrdec ) + end + + return str +end + +--- Extract and split urlencoded data pairs, separated bei either "&" or ";" +-- from given url or string. Returns a table with urldecoded values. +-- Simple parameters are stored as string values associated with the parameter +-- name within the table. Parameters with multiple values are stored as array +-- containing the corresponding values. +-- @param url The url or string which contains x-www-urlencoded form data +-- @param tbl Use the given table for storing values (optional) +-- @return Table containing the urldecoded parameters +-- @see urlencode_params +function urldecode_params( url, tbl ) + + local params = tbl or { } + + if url:find("?") then + url = url:gsub( "^.+%?([^?]+)", "%1" ) + end + + for pair in url:gmatch( "[^&;]+" ) do + + -- find key and value + local key = urldecode( pair:match("^([^=]+)") ) + local val = urldecode( pair:match("^[^=]+=(.+)$") ) + + -- store + if type(key) == "string" and key:len() > 0 then + if type(val) ~= "string" then val = "" end + + if not params[key] then + params[key] = val + elseif type(params[key]) ~= "table" then + params[key] = { params[key], val } + else + table.insert( params[key], val ) + end + end + end + + return params +end + +--- Encode given string to x-www-urlencoded format. +-- @param str String to encode +-- @return String containing the encoded data +-- @see urldecode +function urlencode( str ) + + local function __chrenc( chr ) + return string.format( + "%%%02x", string.byte( chr ) + ) + end + + if type(str) == "string" then + str = str:gsub( + "([^a-zA-Z0-9$_%-%.%+!*'(),])", + __chrenc + ) + end + + return str +end + +function xqurlencode(str) + if (str) then + --Ensure all newlines are in CRLF form + str = string.gsub (str, "\r?\n", "\r\n") + + --Percent-encode all non-unreserved characters + --as per RFC 3986, Section 2.3 + --(except for space, which gets plus-encoded) + str = string.gsub (str, "([^%w%-%.%_%~ ])", + function (c) return string.format ("%%%02X", string.byte(c)) end) + + --Convert spaces to plus signs + str = string.gsub (str, " ", "+") + end + return str +end + +function xq_urlencode_params( tbl ) + local enc = "" + + for k, v in pairs(tbl) do + if type(v) == "table" then + for i, v2 in ipairs(v) do + enc = enc .. ( #enc > 0 and "&" or "" ) .. + urlencode(k) .. "=" .. xqurlencode(v2) + end + else + enc = (enc .. ( #enc > 0 and "&" or "" ) .. + urlencode(k) .. "=" .. xqurlencode(v)) + end + end + return enc +end + +--- Encode each key-value-pair in given table to x-www-urlencoded format, +-- separated by "&". Tables are encoded as parameters with multiple values by +-- repeating the parameter name with each value. +-- @param tbl Table with the values +-- @return String containing encoded values +-- @see urldecode_params +function urlencode_params( tbl ) + local enc = "" + + for k, v in pairs(tbl) do + if type(v) == "table" then + for i, v2 in ipairs(v) do + enc = enc .. ( #enc > 0 and "&" or "" ) .. + urlencode(k) .. "=" .. urlencode(v2) + end + else + enc = (enc .. ( #enc > 0 and "&" or "" ) .. + urlencode(k) .. "=" .. urlencode(v)) + end + end + return enc +end + +-- (Internal function) +-- Initialize given parameter and coerce string into table when the parameter +-- already exists. +-- @param tbl Table where parameter should be created +-- @param key Parameter name +-- @return Always nil +local function __initval( tbl, key ) + if tbl[key] == nil then + tbl[key] = "" + elseif type(tbl[key]) == "string" then + tbl[key] = { tbl[key], "" } + else + table.insert( tbl[key], "" ) + end +end + +-- (Internal function) +-- Append given data to given parameter, either by extending the string value +-- or by appending it to the last string in the parameter's value table. +-- @param tbl Table containing the previously initialized parameter value +-- @param key Parameter name +-- @param chunk String containing the data to append +-- @return Always nil +-- @see __initval +local function __appendval( tbl, key, chunk ) + if type(tbl[key]) == "table" then + tbl[key][#tbl[key]] = tbl[key][#tbl[key]] .. chunk + else + tbl[key] = tbl[key] .. chunk + end +end + +-- (Internal function) +-- Finish the value of given parameter, either by transforming the string value +-- or - in the case of multi value parameters - the last element in the +-- associated values table. +-- @param tbl Table containing the previously initialized parameter value +-- @param key Parameter name +-- @param handler Function which transforms the parameter value +-- @return Always nil +-- @see __initval +-- @see __appendval +local function __finishval( tbl, key, handler ) + if handler then + if type(tbl[key]) == "table" then + tbl[key][#tbl[key]] = handler( tbl[key][#tbl[key]] ) + else + tbl[key] = handler( tbl[key] ) + end + end +end + + +-- Table of our process states +local process_states = { } + +-- Extract "magic", the first line of a http message. +-- Extracts the message type ("get", "post" or "response"), the requested uri +-- or the status code if the line descripes a http response. +process_states['magic'] = function( msg, chunk, err ) + + if chunk ~= nil then + -- ignore empty lines before request + if #chunk == 0 then + return true, nil + end + + -- Is it a request? + local method, uri, http_ver = chunk:match("^([A-Z]+) ([^ ]+) HTTP/([01]%.[019])$") + + -- Yup, it is + if method then + + msg.type = "request" + msg.request_method = method:lower() + msg.request_uri = uri + msg.http_version = tonumber( http_ver ) + msg.headers = { } + + -- We're done, next state is header parsing + return true, function( chunk ) + return process_states['headers']( msg, chunk ) + end + + -- Is it a response? + else + + local http_ver, code, message = chunk:match("^HTTP/([01]%.[019]) ([0-9]+) ([^\r\n]+)$") + + -- Is a response + if code then + + msg.type = "response" + msg.status_code = code + msg.status_message = message + msg.http_version = tonumber( http_ver ) + msg.headers = { } + + -- We're done, next state is header parsing + return true, function( chunk ) + return process_states['headers']( msg, chunk ) + end + end + end + end + + -- Can't handle it + return nil, "Invalid HTTP message magic" +end + + +-- Extract headers from given string. +process_states['headers'] = function( msg, chunk ) + + if chunk ~= nil then + + -- Look for a valid header format + local hdr, val = chunk:match( "^([A-Za-z][A-Za-z0-9%-_]+): +(.+)$" ) + + if type(hdr) == "string" and hdr:len() > 0 and + type(val) == "string" and val:len() > 0 + then + msg.headers[hdr] = val + + -- Valid header line, proceed + return true, nil + + elseif #chunk == 0 then + -- Empty line, we won't accept data anymore + return false, nil + else + -- Junk data + return nil, "Invalid HTTP header received" + end + else + return nil, "Unexpected EOF" + end +end + + +--- Creates a ltn12 source from the given socket. The source will return it's +-- data line by line with the trailing \r\n stripped of. +-- @param sock Readable network socket +-- @return Ltn12 source function +function header_source( sock ) + return ltn12.source.simplify( function() + + local chunk, err, part = sock:receive("*l") + + -- Line too long + if chunk == nil then + if err ~= "timeout" then + return nil, part + and "Line exceeds maximum allowed length" + or "Unexpected EOF" + else + return nil, err + end + + -- Line ok + elseif chunk ~= nil then + + -- Strip trailing CR + chunk = chunk:gsub("\r$","") + + return chunk, nil + end + end ) +end + +--- Decode a mime encoded http message body with multipart/form-data +-- Content-Type. Stores all extracted data associated with its parameter name +-- in the params table withing the given message object. Multiple parameter +-- values are stored as tables, ordinary ones as strings. +-- If an optional file callback function is given then it is feeded with the +-- file contents chunk by chunk and only the extracted file name is stored +-- within the params table. The callback function will be called subsequently +-- with three arguments: +-- o Table containing decoded (name, file) and raw (headers) mime header data +-- o String value containing a chunk of the file data +-- o Boolean which indicates wheather the current chunk is the last one (eof) +-- @param src Ltn12 source function +-- @param msg HTTP message object +-- @param filecb File callback function (optional) +-- @return Value indicating successful operation (not nil means "ok") +-- @return String containing the error if unsuccessful +-- @see parse_message_header +function mimedecode_message_body( src, msg, filecb ) + + if msg and msg.env.CONTENT_TYPE then + msg.mime_boundary = msg.env.CONTENT_TYPE:match("^multipart/form%-data; boundary=(.+)$") + end + + if not msg.mime_boundary then + return nil, "Invalid Content-Type found" + end + + + local tlen = 0 + local inhdr = false + local field = nil + local store = nil + local lchunk = nil + + local function parse_headers( chunk, field ) + + local stat + repeat + chunk, stat = chunk:gsub( + "^([A-Z][A-Za-z0-9%-_]+): +([^\r\n]+)\r\n", + function(k,v) + field.headers[k] = v + return "" + end + ) + until stat == 0 + + chunk, stat = chunk:gsub("^\r\n","") + + -- End of headers + if stat > 0 then + if field.headers["Content-Disposition"] then + if field.headers["Content-Disposition"]:match("^form%-data; ") then + field.name = field.headers["Content-Disposition"]:match('name="(.-)"') + field.file = field.headers["Content-Disposition"]:match('filename="(.+)"$') + end + end + + if not field.headers["Content-Type"] then + field.headers["Content-Type"] = "text/plain" + end + + if field.name and field.file and filecb then + __initval( msg.params, field.name ) + __appendval( msg.params, field.name, field.file ) + + store = filecb + elseif field.name then + __initval( msg.params, field.name ) + + store = function( hdr, buf, eof ) + __appendval( msg.params, field.name, buf ) + end + else + store = nil + end + + return chunk, true + end + + return chunk, false + end + + local function snk( chunk ) + + tlen = tlen + ( chunk and #chunk or 0 ) + + if msg.env.CONTENT_LENGTH and tlen > tonumber(msg.env.CONTENT_LENGTH) + 2 then + return nil, "Message body size exceeds Content-Length" + end + + if chunk and not lchunk then + lchunk = "\r\n" .. chunk + + elseif lchunk then + local data = lchunk .. ( chunk or "" ) + local spos, epos, found + + repeat + spos, epos = data:find( "\r\n--" .. msg.mime_boundary .. "\r\n", 1, true ) + + if not spos then + spos, epos = data:find( "\r\n--" .. msg.mime_boundary .. "--\r\n", 1, true ) + end + + + if spos then + local predata = data:sub( 1, spos - 1 ) + + if inhdr then + predata, eof = parse_headers( predata, field ) + + if not eof then + return nil, "Invalid MIME section header" + elseif not field.name then + return nil, "Invalid Content-Disposition header" + end + end + + if store then + store( field, predata, true ) + end + + + field = { headers = { } } + found = found or true + + data, eof = parse_headers( data:sub( epos + 1, #data ), field ) + inhdr = not eof + end + until not spos + + if found then + -- We found at least some boundary. Save + -- the unparsed remaining data for the + -- next chunk. + lchunk, data = data, nil + else + -- There was a complete chunk without a boundary. Parse it as headers or + -- append it as data, depending on our current state. + if inhdr then + lchunk, eof = parse_headers( data, field ) + inhdr = not eof + else + -- We're inside data, so append the data. Note that we only append + -- lchunk, not all of data, since there is a chance that chunk + -- contains half a boundary. Assuming that each chunk is at least the + -- boundary in size, this should prevent problems + store( field, lchunk, false ) + lchunk, chunk = chunk, nil + end + end + end + + return true + end + + return ltn12.pump.all( src, snk ) +end + +--- Decode an urlencoded http message body with application/x-www-urlencoded +-- Content-Type. Stores all extracted data associated with its parameter name +-- in the params table withing the given message object. Multiple parameter +-- values are stored as tables, ordinary ones as strings. +-- @param src Ltn12 source function +-- @param msg HTTP message object +-- @return Value indicating successful operation (not nil means "ok") +-- @return String containing the error if unsuccessful +-- @see parse_message_header +function urldecode_message_body( src, msg ) + + local tlen = 0 + local lchunk = nil + + local function snk( chunk ) + + tlen = tlen + ( chunk and #chunk or 0 ) + + if msg.env.CONTENT_LENGTH and tlen > tonumber(msg.env.CONTENT_LENGTH) + 2 then + return nil, "Message body size exceeds Content-Length" + elseif tlen > HTTP_MAX_CONTENT then + return nil, "Message body size exceeds maximum allowed length" + end + + if not lchunk and chunk then + lchunk = chunk + + elseif lchunk then + local data = lchunk .. ( chunk or "&" ) + local spos, epos + + repeat + spos, epos = data:find("^.-[;&]") + + if spos then + local pair = data:sub( spos, epos - 1 ) + local key = pair:match("^(.-)=") + local val = pair:match("=([^%s]*)%s*$") + + if key and #key > 0 then + __initval( msg.params, key ) + __appendval( msg.params, key, val ) + __finishval( msg.params, key, urldecode ) + else + key = "invalid_param" + __initval( msg.params, key ) + __appendval( msg.params, key, pair ) + __finishval( msg.params, key, urldecode ) + end + + data = data:sub( epos + 1, #data ) + end + until not spos + + lchunk = data + end + + return true + end + + return ltn12.pump.all( src, snk ) +end + +--- Try to extract an http message header including information like protocol +-- version, message headers and resulting CGI environment variables from the +-- given ltn12 source. +-- @param src Ltn12 source function +-- @return HTTP message object +-- @see parse_message_body +function parse_message_header( src ) + + local ok = true + local msg = { } + + local sink = ltn12.sink.simplify( + function( chunk ) + return process_states['magic']( msg, chunk ) + end + ) + + -- Pump input data... + while ok do + + -- get data + ok, err = ltn12.pump.step( src, sink ) + + -- error + if not ok and err then + return nil, err + + -- eof + elseif not ok then + + -- Process get parameters + if ( msg.request_method == "get" or msg.request_method == "post" ) and + msg.request_uri:match("?") + then + msg.params = urldecode_params( msg.request_uri ) + else + msg.params = { } + end + + -- Populate common environment variables + msg.env = { + CONTENT_LENGTH = msg.headers['Content-Length']; + CONTENT_TYPE = msg.headers['Content-Type'] or msg.headers['Content-type']; + REQUEST_METHOD = msg.request_method:upper(); + REQUEST_URI = msg.request_uri; + SCRIPT_NAME = msg.request_uri:gsub("?.+$",""); + SCRIPT_FILENAME = ""; -- XXX implement me + SERVER_PROTOCOL = "HTTP/" .. string.format("%.1f", msg.http_version); + QUERY_STRING = msg.request_uri:match("?") + and msg.request_uri:gsub("^.+?","") or "" + } + + -- Populate HTTP_* environment variables + for i, hdr in ipairs( { + 'Accept', + 'Accept-Charset', + 'Accept-Encoding', + 'Accept-Language', + 'Connection', + 'Cookie', + 'Host', + 'Referer', + 'User-Agent', + } ) do + local var = 'HTTP_' .. hdr:upper():gsub("%-","_") + local val = msg.headers[hdr] + + msg.env[var] = val + end + end + end + + return msg +end + +--- Try to extract and decode a http message body from the given ltn12 source. +-- This function will examine the Content-Type within the given message object +-- to select the appropriate content decoder. +-- Currently the application/x-www-urlencoded and application/form-data +-- mime types are supported. If the encountered content encoding can't be +-- handled then the whole message body will be stored unaltered as "content" +-- property within the given message object. +-- @param src Ltn12 source function +-- @param msg HTTP message object +-- @param filecb File data callback (optional, see mimedecode_message_body()) +-- @return Value indicating successful operation (not nil means "ok") +-- @return String containing the error if unsuccessful +-- @see parse_message_header +function parse_message_body( src, msg, filecb ) + -- Is it multipart/mime ? + if msg.env.REQUEST_METHOD == "POST" and msg.env.CONTENT_TYPE and + msg.env.CONTENT_TYPE:match("^multipart/form%-data") + then + + return mimedecode_message_body( src, msg, filecb ) + + -- Is it application/x-www-form-urlencoded ? + elseif msg.env.REQUEST_METHOD == "POST" and msg.env.CONTENT_TYPE and + msg.env.CONTENT_TYPE:match("^application/x%-www%-form%-urlencoded") + then + return urldecode_message_body( src, msg, filecb ) + + + -- Unhandled encoding + -- If a file callback is given then feed it chunk by chunk, else + -- store whole buffer in message.content + else + + local sink + + -- If we have a file callback then feed it + if type(filecb) == "function" then + sink = filecb + + -- ... else append to .content + else + msg.content = "" + msg.content_length = 0 + + sink = function( chunk, err ) + if chunk then + if ( msg.content_length + #chunk ) <= HTTP_MAX_CONTENT then + msg.content = msg.content .. chunk + msg.content_length = msg.content_length + #chunk + return true + else + return nil, "POST data exceeds maximum allowed length" + end + end + return true + end + end + + -- Pump data... + while true do + local ok, err = ltn12.pump.step( src, sink ) + + if not ok and err then + return nil, err + elseif not err then + return true + end + end + + return true + end +end + +--- Table containing human readable messages for several http status codes. +-- @class table +statusmsg = { + [200] = "OK", + [206] = "Partial Content", + [301] = "Moved Permanently", + [302] = "Found", + [304] = "Not Modified", + [400] = "Bad Request", + [403] = "Forbidden", + [404] = "Not Found", + [405] = "Method Not Allowed", + [408] = "Request Time-out", + [411] = "Length Required", + [412] = "Precondition Failed", + [416] = "Requested range not satisfiable", + [500] = "Internal Server Error", + [503] = "Server Unavailable", +} diff --git a/Mi_Lua/luci/http/protocol/conditionals.lua b/Mi_Lua/luci/http/protocol/conditionals.lua new file mode 100644 index 0000000..664d8df --- /dev/null +++ b/Mi_Lua/luci/http/protocol/conditionals.lua @@ -0,0 +1,153 @@ +--[[ + +HTTP protocol implementation for LuCI - RFC2616 / 14.19, 14.24 - 14.28 +(c) 2008 Freifunk Leipzig / Jo-Philipp Wich + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +$Id: conditionals.lua 5637 2009-12-20 18:35:05Z jow $ + +]]-- + +--- LuCI http protocol implementation - HTTP/1.1 bits. +-- This class provides basic ETag handling and implements most of the +-- conditional HTTP/1.1 headers specified in RFC2616 Sct. 14.24 - 14.28 . +module("luci.http.protocol.conditionals", package.seeall) + +local date = require("luci.http.protocol.date") + + +--- Implement 14.19 / ETag. +-- @param stat A file.stat structure +-- @return String containing the generated tag suitable for ETag headers +function mk_etag( stat ) + if stat ~= nil then + return string.format( '"%x-%x-%x"', stat.ino, stat.size, stat.mtime ) + end +end + +--- 14.24 / If-Match +-- Test whether the given message object contains an "If-Match" header and +-- compare it against the given stat object. +-- @param req HTTP request message object +-- @param stat A file.stat object +-- @return Boolean indicating whether the precondition is ok +-- @return Alternative status code if the precondition failed +function if_match( req, stat ) + local h = req.headers + local etag = mk_etag( stat ) + + -- Check for matching resource + if type(h['If-Match']) == "string" then + for ent in h['If-Match']:gmatch("([^, ]+)") do + if ( ent == '*' or ent == etag ) and stat ~= nil then + return true + end + end + + return false, 412 + end + + return true +end + +--- 14.25 / If-Modified-Since +-- Test whether the given message object contains an "If-Modified-Since" header +-- and compare it against the given stat object. +-- @param req HTTP request message object +-- @param stat A file.stat object +-- @return Boolean indicating whether the precondition is ok +-- @return Alternative status code if the precondition failed +-- @return Table containing extra HTTP headers if the precondition failed +function if_modified_since( req, stat ) + local h = req.headers + + -- Compare mtimes + if type(h['If-Modified-Since']) == "string" then + local since = date.to_unix( h['If-Modified-Since'] ) + + if stat == nil or since < stat.mtime then + return true + end + + return false, 304, { + ["ETag"] = mk_etag( stat ); + ["Date"] = date.to_http( os.time() ); + ["Last-Modified"] = date.to_http( stat.mtime ) + } + end + + return true +end + +--- 14.26 / If-None-Match +-- Test whether the given message object contains an "If-None-Match" header and +-- compare it against the given stat object. +-- @param req HTTP request message object +-- @param stat A file.stat object +-- @return Boolean indicating whether the precondition is ok +-- @return Alternative status code if the precondition failed +-- @return Table containing extra HTTP headers if the precondition failed +function if_none_match( req, stat ) + local h = req.headers + local etag = mk_etag( stat ) + local method = req.env and req.env.REQUEST_METHOD or "GET" + + -- Check for matching resource + if type(h['If-None-Match']) == "string" then + for ent in h['If-None-Match']:gmatch("([^, ]+)") do + if ( ent == '*' or ent == etag ) and stat ~= nil then + if method == "GET" or method == "HEAD" then + return false, 304, { + ["ETag"] = etag; + ["Date"] = date.to_http( os.time() ); + ["Last-Modified"] = date.to_http( stat.mtime ) + } + else + return false, 412 + end + end + end + end + + return true +end + +--- 14.27 / If-Range +-- The If-Range header is currently not implemented due to the lack of general +-- byte range stuff in luci.http.protocol . This function will always return +-- false, 412 to indicate a failed precondition. +-- @param req HTTP request message object +-- @param stat A file.stat object +-- @return Boolean indicating whether the precondition is ok +-- @return Alternative status code if the precondition failed +function if_range( req, stat ) + -- Sorry, no subranges (yet) + return false, 412 +end + +--- 14.28 / If-Unmodified-Since +-- Test whether the given message object contains an "If-Unmodified-Since" +-- header and compare it against the given stat object. +-- @param req HTTP request message object +-- @param stat A file.stat object +-- @return Boolean indicating whether the precondition is ok +-- @return Alternative status code if the precondition failed +function if_unmodified_since( req, stat ) + local h = req.headers + + -- Compare mtimes + if type(h['If-Unmodified-Since']) == "string" then + local since = date.to_unix( h['If-Unmodified-Since'] ) + + if stat ~= nil and since <= stat.mtime then + return false, 412 + end + end + + return true +end diff --git a/Mi_Lua/luci/http/protocol/date.lua b/Mi_Lua/luci/http/protocol/date.lua new file mode 100644 index 0000000..db7b524 --- /dev/null +++ b/Mi_Lua/luci/http/protocol/date.lua @@ -0,0 +1,115 @@ +--[[ + +HTTP protocol implementation for LuCI - date handling +(c) 2008 Freifunk Leipzig / Jo-Philipp Wich + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +$Id: date.lua 3860 2008-12-06 03:18:14Z jow $ + +]]-- + +--- LuCI http protocol implementation - date helper class. +-- This class contains functions to parse, compare and format http dates. +module("luci.http.protocol.date", package.seeall) + +require("luci.sys.zoneinfo") + + +MONTHS = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", + "Sep", "Oct", "Nov", "Dec" +} + +--- Return the time offset in seconds between the UTC and given time zone. +-- @param tz Symbolic or numeric timezone specifier +-- @return Time offset to UTC in seconds +function tz_offset(tz) + + if type(tz) == "string" then + + -- check for a numeric identifier + local s, v = tz:match("([%+%-])([0-9]+)") + if s == '+' then s = 1 else s = -1 end + if v then v = tonumber(v) end + + if s and v then + return s * 60 * ( math.floor( v / 100 ) * 60 + ( v % 100 ) ) + + -- lookup symbolic tz + elseif luci.sys.zoneinfo.OFFSET[tz:lower()] then + return luci.sys.zoneinfo.OFFSET[tz:lower()] + end + + end + + -- bad luck + return 0 +end + +--- Parse given HTTP date string and convert it to unix epoch time. +-- @param data String containing the date +-- @return Unix epoch time +function to_unix(date) + + local wd, day, mon, yr, hr, min, sec, tz = date:match( + "([A-Z][a-z][a-z]), ([0-9]+) " .. + "([A-Z][a-z][a-z]) ([0-9]+) " .. + "([0-9]+):([0-9]+):([0-9]+) " .. + "([A-Z0-9%+%-]+)" + ) + + if day and mon and yr and hr and min and sec then + -- find month + local month = 1 + for i = 1, 12 do + if MONTHS[i] == mon then + month = i + break + end + end + + -- convert to epoch time + return tz_offset(tz) + os.time( { + year = yr, + month = month, + day = day, + hour = hr, + min = min, + sec = sec + } ) + end + + return 0 +end + +--- Convert the given unix epoch time to valid HTTP date string. +-- @param time Unix epoch time +-- @return String containing the formatted date +function to_http(time) + return os.date( "%a, %d %b %Y %H:%M:%S GMT", time ) +end + +--- Compare two dates which can either be unix epoch times or HTTP date strings. +-- @param d1 The first date or epoch time to compare +-- @param d2 The first date or epoch time to compare +-- @return -1 - if d1 is lower then d2 +-- @return 0 - if both dates are equal +-- @return 1 - if d1 is higher then d2 +function compare(d1, d2) + + if d1:match("[^0-9]") then d1 = to_unix(d1) end + if d2:match("[^0-9]") then d2 = to_unix(d2) end + + if d1 == d2 then + return 0 + elseif d1 < d2 then + return -1 + else + return 1 + end +end diff --git a/Mi_Lua/luci/http/protocol/mime.lua b/Mi_Lua/luci/http/protocol/mime.lua new file mode 100644 index 0000000..9cf0fe4 --- /dev/null +++ b/Mi_Lua/luci/http/protocol/mime.lua @@ -0,0 +1,99 @@ +--[[ + +HTTP protocol implementation for LuCI - mime handling +(c) 2008 Freifunk Leipzig / Jo-Philipp Wich + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +$Id: mime.lua 5327 2009-09-11 01:55:10Z jow $ + +]]-- + +--- LuCI http protocol implementation - mime helper class. +-- This class provides functions to guess mime types from file extensions and +-- vice versa. +module("luci.http.protocol.mime", package.seeall) + +require("luci.util") + +--- MIME mapping table containg extension - mimetype relations. +-- @class table +MIME_TYPES = { + ["txt"] = "text/plain"; + ["js"] = "text/javascript"; + ["css"] = "text/css"; + ["htm"] = "text/html"; + ["html"] = "text/html"; + ["patch"] = "text/x-patch"; + ["c"] = "text/x-csrc"; + ["h"] = "text/x-chdr"; + ["o"] = "text/x-object"; + ["ko"] = "text/x-object"; + + ["bmp"] = "image/bmp"; + ["gif"] = "image/gif"; + ["png"] = "image/png"; + ["jpg"] = "image/jpeg"; + ["jpeg"] = "image/jpeg"; + ["svg"] = "image/svg+xml"; + + ["zip"] = "application/zip"; + ["pdf"] = "application/pdf"; + ["xml"] = "application/xml"; + ["xsl"] = "application/xml"; + ["doc"] = "application/msword"; + ["ppt"] = "application/vnd.ms-powerpoint"; + ["xls"] = "application/vnd.ms-excel"; + ["odt"] = "application/vnd.oasis.opendocument.text"; + ["odp"] = "application/vnd.oasis.opendocument.presentation"; + ["pl"] = "application/x-perl"; + ["sh"] = "application/x-shellscript"; + ["php"] = "application/x-php"; + ["deb"] = "application/x-deb"; + ["iso"] = "application/x-cd-image"; + ["tgz"] = "application/x-compressed-tar"; + + ["mp3"] = "audio/mpeg"; + ["ogg"] = "audio/x-vorbis+ogg"; + ["wav"] = "audio/x-wav"; + + ["mpg"] = "video/mpeg"; + ["mpeg"] = "video/mpeg"; + ["avi"] = "video/x-msvideo"; +} + +--- Extract extension from a filename and return corresponding mime-type or +-- "application/octet-stream" if the extension is unknown. +-- @param filename The filename for which the mime type is guessed +-- @return String containign the determined mime type +function to_mime(filename) + if type(filename) == "string" then + local ext = filename:match("[^%.]+$") + + if ext and MIME_TYPES[ext:lower()] then + return MIME_TYPES[ext:lower()] + end + end + + return "application/octet-stream" +end + +--- Return corresponding extension for a given mime type or nil if the +-- given mime-type is unknown. +-- @param mimetype The mimetype to retrieve the extension from +-- @return String with the extension or nil for unknown type +function to_ext(mimetype) + if type(mimetype) == "string" then + for ext, type in luci.util.kspairs( MIME_TYPES ) do + if type == mimetype then + return ext + end + end + end + + return nil +end diff --git a/Mi_Lua/luci/i18n.lua b/Mi_Lua/luci/i18n.lua new file mode 100644 index 0000000..5c8aba2 --- /dev/null +++ b/Mi_Lua/luci/i18n.lua @@ -0,0 +1,104 @@ +--[[ +LuCI - Internationalisation + +Description: +A very minimalistic but yet effective internationalisation module + +FileId: +$Id: i18n.lua 9558 2012-12-18 13:58:22Z jow $ + +License: +Copyright 2008 Steven Barth + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +]]-- + +--- LuCI translation library. +module("luci.i18n", package.seeall) +require("luci.util") + +local tparser = require "luci.template.parser" + +table = {} +i18ndir = luci.util.libpath() .. "/i18n/" +loaded = {} +context = luci.util.threadlocal() +default = "en" + +--- Clear the translation table. +function clear() +end + +--- Load a translation and copy its data into the translation table. +-- @param file Language file +-- @param lang Two-letter language code +-- @param force Force reload even if already loaded (optional) +-- @return Success status +function load(file, lang, force) +end + +--- Load a translation file using the default translation language. +-- Alternatively load the translation of the fallback language. +-- @param file Language file +-- @param force Force reload even if already loaded (optional) +function loadc(file, force) +end + +--- Set the context default translation language. +-- @param lang Two-letter language code +function setlanguage(lang) + context.lang = lang:gsub("_", "-") + context.parent = (context.lang:match("^([a-z][a-z])_")) + if not tparser.load_catalog(context.lang, i18ndir) then + if context.parent then + tparser.load_catalog(context.parent, i18ndir) + return context.parent + end + end + return context.lang +end + +--- Return the translated value for a specific translation key. +-- @param key Default translation text +-- @return Translated string +function translate(key) + return tparser.translate(key) or key +end + +--- Return the translated value for a specific translation key and use it as sprintf pattern. +-- @param key Default translation text +-- @param ... Format parameters +-- @return Translated and formatted string +function translatef(key, ...) + return tostring(translate(key)):format(...) +end + +--- Return the translated value for a specific translation key +-- and ensure that the returned value is a Lua string value. +-- This is the same as calling tostring(translate(...)) +-- @param key Default translation text +-- @return Translated string +function string(key) + return tostring(translate(key)) +end + +--- Return the translated value for a specific translation key and use it as sprintf pattern. +-- Ensure that the returned value is a Lua string value. +-- This is the same as calling tostring(translatef(...)) +-- @param key Default translation text +-- @param ... Format parameters +-- @return Translated and formatted string +function stringf(key, ...) + return tostring(translate(key)):format(...) +end diff --git a/Mi_Lua/luci/i18n/base.en.lmo b/Mi_Lua/luci/i18n/base.en.lmo new file mode 100644 index 0000000000000000000000000000000000000000..76ff9dba91c299b357eed7906012a64caec94736 GIT binary patch literal 64 zcmaDkWB<#=^PaC+^JG^~YMw$#YH1XhNK8x3gs?+ggF_e?JigUDyTd=zkO2aO_MKYz R>hs)d3P7$15c2`C3IKaw9)|z` literal 0 HcmV?d00001 diff --git a/Mi_Lua/luci/i18n/firewall.ca.lmo b/Mi_Lua/luci/i18n/firewall.ca.lmo new file mode 100644 index 0000000000000000000000000000000000000000..103d847072fa828ec6976ae3b32e0bddaf9197a8 GIT binary patch literal 716 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 E1Hvf700000 literal 0 HcmV?d00001 diff --git a/Mi_Lua/luci/i18n/firewall.de.lmo b/Mi_Lua/luci/i18n/firewall.de.lmo new file mode 100644 index 0000000000000000000000000000000000000000..9a72186ddcbd351b2cf1f04cc342664a1ad12a82 GIT binary patch literal 7112 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 Fe*s`Pa{2%O literal 0 HcmV?d00001 diff --git a/Mi_Lua/luci/i18n/firewall.el.lmo b/Mi_Lua/luci/i18n/firewall.el.lmo new file mode 100644 index 0000000000000000000000000000000000000000..fdd43acb6bf9b020ba92e56e238b706f30727720 GIT binary patch literal 2672 zcma)7e@xV67=Jk+2!Rg4AMs;;S=~_J&M1Nni8O;06-cF}QE8Z3%TTUJD;K|xA0a3z zPSM$!KwBPn9B|$pc$}$Qw$=M?>db*N+r-7173H#~fAo32-{XfIhHUS6Ki~Ixp7-;7 zp67i|qu1yqeu~>9jb5vjxRF~$8MpEttX|O|ahoXRPTtGgd7r3+nO4hSCrV-E78SDf zDS3AaCNA!VL3+vh;tb7(^!fB3ro2Z~>Ldhoag(GR>Xk#rcoV+1BUl%=;T|hDdICGR z86@lzu_QBdD!gpG5B$j23Mv+E)#-E$F^>rYcOWSXz9On9mtcb6BebhUExhcokSPm; zDCb{*ryE9Y-p$Rlr)&{xgb&GbU!oi&nkgx<3n|z{Iph1`Zxi+0f%{#!+b%85^W2Ge zWnfYv$EZhKZ(Bvv35NWjsE6kW^$Dklb3ioUdiw*eVZ0Hk6Ggks2iM!R$hr$PF^Doz z%U$q97Cj(dNufwTs)?ejTUN!Qc#miSWjuOItJdUbH@;U?v;jVSP!*a{o(N}_Af&e( zli4|x6mc(Zb7E1qxjoDf&IH!P4jspx#IFTawTNnnQSQlGRE&{AB_6^?qGqR%(B)xj zJJp?I@fy&39<>k3B9p39awh-#+KHfrN(+K=6F*#xNqXPYB8o1u#=H|!_M%2qdx${w zrw?vwUWGhKTLI`o8Ew=%cq58lqxtbh>JAJJvaFPMjPCaMnlIdll;)HSERK2G7dG+MDUh|4^Qh7>@8$De-`ewy0 z$^|o#x{P;`Vxo$yDRYCIy%|kP$6)vX{}roNfjbQgH`!ue1<4^gO)Xx7S?0-bqRtYA z06kW@k5B{39f~O>ga0J6WW}PzR9ib9x>>2a_q_488Jjhup5x}v*tZtc8ma#OBc%6F zBNii-*n8k+gkRn^=~c>JtX&QbX?kavn2ZVy;n z_r%O7Omrb{v$QZuL*dzCV zL!);bSl2w<+K7EAmO8p3KlOl53~u)S1voY)I)A$dpCcSwzqTXm)$HAj`5^|2(xmy7 zIXlndiTLHwdEB3~{%%=Ny#(=tfx{D$5;raTtG)&2SS(c6c=4IQI`whzSq>ck)MJC^ z=Y-6x!+sBzI_a&5%jQ|JkvKO3$0YyTzq~YaYZ~(D29A9;zq!bgxg`VqI)Ecml1~)e z9JUo9ekE|of|uXE=(Ex`7keXC(()TWC)9@B{hTr1&A^KbZVe4B3P^>&&vxLr*9$`j z_H-VV2yrXuW!n<;c4KL4jf)me#A2LsDCE*^cTGSjQX{p+4J6qeFX6Aol#kLHy0VZ zaQ}YbD8q%1Zge71_6w`OyJH;88(4_*WMF6AbH~t1+_^(xX$?OtV@}cYTIax7J{|0~9uxUs82DW+*AJFZ)G*i*gY zPSpBce!-D_4e>qzR-61gmoyg~+sjx$w2ap{W?N&ffS)gE{Jg2ZxFm=bZi1eHz%$Kk z`W*GuYi02N2RN#CNAZWL(bX#KiLZ3#xJ$RJf8}AEcLQtAT{^T^^{Wm2<|6?ood5pY zvwiuFi`cKmivQtgX4{RhL*O4k{6hKqtd~C9mIc1PCg4RE4(H~*yQeh={-=Rsel%Qe r2%L8t`ukITf(Gt=6ST8XZ-m}Ufal)2*>*c^==6Eqp9ri*+{FI?Lyvu1 literal 0 HcmV?d00001 diff --git a/Mi_Lua/luci/i18n/firewall.es.lmo b/Mi_Lua/luci/i18n/firewall.es.lmo new file mode 100644 index 0000000000000000000000000000000000000000..078e225d3c2754a3660d37e12095aeb671f71027 GIT binary patch literal 7136 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 Hm@V#KbQF4O literal 0 HcmV?d00001 diff --git a/Mi_Lua/luci/i18n/firewall.fr.lmo b/Mi_Lua/luci/i18n/firewall.fr.lmo new file mode 100644 index 0000000000000000000000000000000000000000..885e9bf3f24bf166dc4f4742204ddf18de4a5129 GIT binary patch literal 3144 zcmb_eX>3$g6uzZ}7O54m0SSwjrM3;UEVh_H+W=*;QtHT}f*5$bxiegx_ulj^BeDvJ zfQU#937}z38zx0SAc_PO8B8P*P{NKeh9HZH#9$HX68-Ld@3li4f0#(}I`_NFIp00! zJLeKmOhrrVv`uDFC8p?VB5nnksWBTa9V5$dOqG=k z#Kf_!!wC8Jb1`OGH1fbVy zg&`}Nh^survCkSA1bd5tognC21s~XIIXMl?Th3L4b;D$)Bp6j-(Y7epV@ia>9DI&c zZ4Un;8d1}*86aYz8oCS-qm!mq6@(&&5lr3-jtHG9s5&N95l==%*sw?PVsmX~>MWp` ztRTc3|CbifazhXCuw%;Dh#SdsATriKxCKp^=GyfofSnMA6o4Q1iU7hTZ{yv~ zLzP_Pn3i$XkRc_EyC6w(pc@_0wd4&6!G#ejafk*TI$_cz@SmVq)ea6!el5yqMfrpP z;%y?$Q9}(8$$g(Cyv-v5(bw}Dv6_N0GNDwj-h`XQW0*VMugk4nF*l^2{rXxx8Rx^2+hlZLQ%rvoZ=wEUV|uq-Gorsv;+w?^l({>=|a4qkW*bU(3?!Q>qbsC>?DbUhJSU} zCb|uZ3#m(@*W^TKEQwqzZJf|$)9XyF{3FA{5bVKhAt{$l-SXapU?LvkIv3GH zK20NP@w4$S~CBvO!|SoF)5>JLOr%6W3ZT5HJ<%;Oz>t zIlX=>f<kD(h2ao?sL{#)<*Me`B~!@co0l5B{Jr-08AMPQ+rsK&He!YfNQPl&Ad zt1D{gjUT`i77;!ro)O%C7$X_ZFuV{z`zK>%C^$-lg}-bFm%(&g>IEwC$Je zJ=+nIj`fu7efFH&SKoWhV5}d+`YU}KyWYRsv7`}r18_#pu(T!Xw!PICdoKca$-UF{ zx7{~a`~v^ifIIXbJRtmP`>zgTe-GgF!Lzc@cN?%~2gcvwd-Tb=i#7cdMN7fwUEqqM z#>iZy@-coJU&r0&?|dAdaS7ULvlY1O-Z5jwFB&!ZbA;4Yu6dU0*T%2hoP z-|v8*Kh$O6uByC-1sG@J8+vTVk-|$`YCpyK65!6q&u*+aHz?(IjQ5H?CpV9&+uXY& z?$bUEIQ^6|cHHsq$AD8-0{1z!KTPiz-yIG9BEGq2-hVdGI7IyrW5NH?3(L>Hb8y@t p+&8r!aO%(3>l!jf1-``juYub)e!pSPd-bPJ5Yl!g=B{8W`yaA$O@aUb literal 0 HcmV?d00001 diff --git a/Mi_Lua/luci/i18n/firewall.hu.lmo b/Mi_Lua/luci/i18n/firewall.hu.lmo new file mode 100644 index 0000000000000000000000000000000000000000..4ff58c8a81c99dc36993819115a85ec3c7fde04c GIT binary patch literal 6404 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 literal 0 HcmV?d00001 diff --git a/Mi_Lua/luci/i18n/firewall.it.lmo b/Mi_Lua/luci/i18n/firewall.it.lmo new file mode 100644 index 0000000000000000000000000000000000000000..d8e6e8d37862f5beaf138102fb1a490941481a00 GIT binary patch literal 1464 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 literal 0 HcmV?d00001 diff --git a/Mi_Lua/luci/i18n/firewall.no.lmo b/Mi_Lua/luci/i18n/firewall.no.lmo new file mode 100644 index 0000000000000000000000000000000000000000..64765854c474a5ffdf93d05ef1aad43b8a286274 GIT binary patch literal 2896 zcmbVNd2AF_9G;d!u`S2SQ3{1T3Khz+6)2TpTP#qZ>0zZTNG*oh?%U~fu4QI+0Z}3d z$RQvkq9P$2Z51IvfJiKdM2M(SqF4%)OA!euhfxGeB>KIX*=`H`N)FQCSDjUpS_*~Wm zfu)TP5nK^>WbvrDpJbR+R|yMwtj?>d64b+AGL)1;>Y-{NzaeJHfQn-gxpkuArZyLe z<0c}PIz?h1vXEaA${?CR@Xag;s_dKLd^1sbA8+!U<(SpXT12eEBbTpQA?KHblaG5k zdsgPhQbDJxL7Y2tMWv;rKvPX!lVvh+oXjf$m`US`c(bCCh^i`Sio(iFOr7_Ol7!?a z1jW$N2h=2rW+G`=T3JeGJ~Tx?TH`~4i}{f1F|Clf5?wPj2~H4CBx2Z6m?Ic^oGW%m zyCTdk2^_6~m~}s@Lg#>;*SgSZ<4A9+`WanTV!TGNyz1Q1g_S%{`Ze05w&} zQ4pCH2w=8UHp3RtrOwJqcaCircKR%z7V9}J_IS&TDq93e!k${RcPj@L%ONizmZRoK z*{maU#<^4tQPHp*4G&16IN^H-r*KI0u+wz z*|TeJ`9aJEn{%iBmNg0%V5DzZ3-g0eg)qA--O<%wzouM2jr9%u6O#&_+%;+T z@x#zO2Hdu%abeDwFXC4Me+L|wJa^s9`ybVw!kYcsC%@r&=3Yh0MUHE82e?D;WY3({ zwLe@2X7Zi;WWSWXY|Gk@;lC?zuRb}K|BbF*h_mQ=;7NT~mhH|MbnQ6sc;NVyZFQU1 zuaOZ;yCH~CN!fL3Pt(piP-XNSoR3W%9kU>5RGaxYI|H0J;E%++a?0=(92XM<95;B_ z5TUI4@KDGn0LKpVc5PVKf9X`L*?n9i=8swa%Ih^}q0i(;ji^1dc1+sWe<9vv;O8=$ z8VeWR^^C;%QsDL@J?piixV-7G_a*QXnHk&5H~e`BvQhuA^Xxa0ia-19Zx8g}0v?dP z^+C<#`j=ZkFL?+hHb zeOXMy_ZeMc5%2H72|L>EnUs0y1>`^W3UIp}je2NRhn1tSUW9+l?!3H$`oZ;eIIjlo zzJJ+^tA~%invMIF0}rk1{@RY810Sx(n&9vLeCr{1KjA#$i+&o|b*%pVwT;`+9vsK+ z+x6Rxd9|Ak?>Ps1=YeBS@DnB`HYK4xQEvnHKCxFIE7!=ZUi*N1o&4bWlG$Y!qOoTE xEe4;W77^);s{Qo_QS=E{{Z_&(=Px3 literal 0 HcmV?d00001 diff --git a/Mi_Lua/luci/i18n/firewall.pl.lmo b/Mi_Lua/luci/i18n/firewall.pl.lmo new file mode 100644 index 0000000000000000000000000000000000000000..3493ab64a1ecfe303b095141dd3903632494e6ff GIT binary patch literal 7160 zcmbVQ4RjRM6&?s7KM)`x0YoH^NFEvptey_NP9%GiXIe|-vhP^MZi{C3TUCC_PcLpb~gyEr<@aZ?wj}S zyZ5{QlN(?mmKzXq1GxbfrY8Z<1$Z#l^U7q&Cx%0!PiG;&9LUG}*qX>xsV*345c6Ye zI-6NGzJm4@7Bai41j21iETq?{V%sWKSZJ(=a3Dc_08T2_rEc8j^vPHXi+)AbZOqOO6EvJ50brc*E6mMh_QHPvpz0uC_^AT?s$v+j z;ej|3oN|zbXIlHW|)}ts+YoAA{3G#x?wKSE6<9VB``v!9S)D>4BUnRO@SM-=`&|e z7XxktgcHl~bH7O5Y5p=z_p>PzrrRwVVq1wuXJjMb$(#tfI{X-}Pmd%{ka>bNh%h$- zCdgM{X^Rg(+75#_M#kwlb~j+e{aN?2dL^6SP&(gRBv0~zk%xKDLTwxg;z6_yn za*KHbb9<13HPI%NQLO@2Wr!@RIPrpX{aPGYNKkpTiW(ACXMVeLA<0Dw5UV`d=MQT6 z#Mej^2x--fUM{b}U*`!SCc19vu5o|60RjPwksb#!kNP0o)7Jhx$1DuP!?t`6tj4B~ zpFlZ~`vzW-U*EujI7K){Ik!X9;`P1w_)!kIPyPRPg70g98?}mf=;SY|64WF(FmRmW z^GQ@)XXzY%iRG0CHI9BX7K1J-)lTucR7Vh6mCDBEh;Xiuh`bthge9H@s9+&QDUntm ze6wxU06WEuWlGd)E9bRaH^d~Y&63I7iWdUG1H5os*@q{egrK5oZ>2fj5=1v5O<*Ul z%&c^LnGKDRrnvXEL>f$;AOj-)28QE2fw<0vBMn1H31Pzo_0R_8)npvYE)uU{rfxUp z`~4YNd5U?>WQ5WdJ59c5=w+2xM|~`}{jW2TRwJ#WeABULPNWM|WCj^}Ti!`FL48J^ zAZMrGi={aellZ^@#y8*J-i4z=AVh3zI=Kh>Il?w1qY3Z_18`Nb1|~3AQY&jj8kY@$ z8h2N;REa1=H(RF;d%W4MSoz5g=d+$8uSIc*)|BtAA@nKlpa+S=QK0^t1tYE0aOup2 z&WVLIG_3`!&Z)Dom$$2CE5_xAUc9|AJlrhPM&D+oX8=>c9AVufTdh#sm6DCkqLwl4 z!mt<$plYLyqrm2gGRX4m4{H|nU>lc+vBvR@TQX~MYz2j5lB`N- ziaksVp#wB)JV0X=Aw`C#^H{HyTmiz0yFK?@CSBUBe zZ*wI+SfD|dTwdcM$_K_EVg}A=oL78xMgDeWICsJ5yUZ8u_{M-TS$jndkeHOBF~jxAh&g6MCFPjL;I_#LnRVV=RnU}TZs2pNHRrVp6DN?T=tAQf z*Qir>BV*Ex%UH-P(XmRF=)-{15f%eH4RU$fS>3vxkAUPKR#-;wvZ9BsibW?9HR=VA zMjE`S((^*R5(YE^6tgg0*KR&HgR`Bk0j;gcPl=iMuo_Gm+zgIzq(SkyiE&gN9S&Jc z-zxZL8uDQNbwoqg9aJ&5@S!Vcd^w5jv(Eh2?FQf2Cm^EZLLa(g!v4-0bxxQGtt^dw_xX;6<4O_&!Llk9cq=U;7%{_pQ8$bL*7+w0o{1GF#&<_F4O=IWb^*A? zjm29hKq`7_6eKI2?h$09htBhc(UD@Nu;YIEG-myB$dM1IPms`#m<~(z$t`=_^tq!3 zF;5QcU^(~-X3p98+Lc2t${mc}`FRtFh7svl38UG;XACmGUv%KCZKm;!B6k7qGM`Va zYQ#6SXd0I#zK$7HbUP$JzDnVHiGt4}g89qC)_9g+HnYn{oio;WLIZ|^KodG2)+4(F zKtSNROb}f{^1S;xWOr5}2E$*v@b#GOllNRJ!{cetsY#tHb`IR#@r)pJqS3x<(wx<| zf4O|_%Yu-+iT0=5UpaV7`i>6-A?a?=x1`NJ-qp2lFZL#sg3jxfK2WV0pAY^CeL(lV zWm?7y3F*}s1$yoSo!mF@r$r+R7v3QVnaQBL^wT%YFMl9+wjgw%{i*$ySB_XTyUR9Q zeGGK^ZT%`2C1kDJfUQr0&g?&C?wI$^f7~bti335i{za$0{dDt$I|U(yws+0fB7KF}S@xJQztG%xjAv*4yMMZ`;Y1qb?LhAnhty;q88NKXf%oS?rwvWn z^X|zXee)pjoea9q&|NJsx=9qt3VGQv-Rt3_P9F#r48E; zEC&?iPbJ0Qyppqd@iRxDI^k72Y3H21AFSV83OtU1?pk{KEFw~dh0Wf zeB00s^3eSwD$b6(aMyrs@Tc?(pl`2yv$%Ert(6DxO#hO8H2Ui+>rbW~g()&YcdI`C z*=O4?UO;?wB>vszIl7(Lt!F}?#3w*^eV{JfLHj6U2ju-6bRWmsm$qH2-qs!WlRmdO zm+tD@_}J2Sp#OZ({iGwQOQsbkdc=bAtXo_3IPQBN z?|TN;?@pL==yz{HAFK%}3mZ1QaAxVPkY7k9deIv_=B_?t$NdR+gHBr9@$9(5iMjYB z-en2sw8f(apZ;-n#uTt3eo2eh{cPgYFE-QyfB2A)xA<=xw_n}e+8Or$2()MEt&_fT z9jpiM6p%u%2iJ|5moVa?QqaIen7{JF&}rM4`w$;JUkBa!C&!U($FK+8*K5_= z|9U#|m+k4ex(Dd0)!BcZ(CBM(W9u={>8r+8oiY}i>l4e}O%PTPL$nc78XQhVXK8h=^OJ<{pOgMEi| z0luGu?$Vt2`^o2$eh7PI_5+>VJp0u?7nbjM5${Rf{>{hLh5tIabuHv;1Ks&~AtO8a zfHWNX)Pv4={>a5w)}2bn`NWq%_jqyQ#Az8{Jp4=C|7*~FTC#0_daC3A`9dcCy1Z2D z=(cUofo0(TD(LK;k5sL7Tv&j7nmQ2lJ-d1>ZSMH1H_zj~D$qlA>uPXpQ$Fw%H2kH# zvGor&P0YgC#A48ydlLqS78Xq{f+%#pXWOBs#$97|*rUTopu0yZJ##N_KOe$#GyY26 zY#IF9Uv#YMi>;$U&)>KIt-UX9(nyvS_`Bnsj1N~fthSB!ytg#XdQUscR}>*yE2Gig5PaYt7kS+i+P+B!h}KIp^~*Y>1NO6`ew zPIv-z)+gt;@9?_bUIW%--?it@JbQV_&;KzB@+<Y^SAyTupa)&La{1*S zbT|upCM1JSyL|s`R|`)K!uf<&&>2_%{G@Bb)XDItG}yDlo^Nw+J$H8M?9)&cX9PR+ G9{3+t#QQM- literal 0 HcmV?d00001 diff --git a/Mi_Lua/luci/i18n/firewall.pt-br.lmo b/Mi_Lua/luci/i18n/firewall.pt-br.lmo new file mode 100644 index 0000000000000000000000000000000000000000..b26426b32e76c384884765f7dbed7ff4aa9b9bac GIT binary patch literal 7332 zcmb_g34Bb~8o#ziNYFI4)N+I&niy4Ui6t1zLoy`P+Pay!GcGf8$GtNV>yKKhw3Mor z+Cs@wOBF4p3#O}Qt2M2)o)#_LXdlJv`+w)&nYl@6z2AF&o#uXL`ObI#-~ap0caBA~ zXw0E_nMD&US{nW^iG&oJFQAG}k!3^7RJ>ASK}hK*xm8n3N_J*Oj%h%XCd{s=%q8Y2 zY8sO?hvFF_vL^V){F16EKG7qO6P*g0g!DX7?O<`HH>JPK?+9pC=Er2cUDMkI;r_;@ zF~96kjJd^h8uJZ(Oky6{D+eSd(ta~Sg`ri7AY?dYhYagftQYylSji~|MAd4wqP>ga zbII>70BM3?msGA)VUo`wdS%}T(JT1^3IlxUlDES+=w?odDXQ$2 zyy;ER)b^fp{30MOKF=v>0okYM6Jbl=py(MLl+=C_df>cVNez%4+&vnD>Il}jCOjvT4Azuf74%i5 zJh3bOikPA@zvxf`07UlXg$g_}cGE-5l|4ieU~GKD$*i}c_;W?aD6$&?V(Rm&(7;JY z8$x`s>_M4LTpmSnn&!h-vKCMk=HuWJdrIt4$`44YPto^c;{t{Vd@p*Rn4!9ZoP>zy ze5^z~=w^V-nlOVZ=CBksQ@^bQktK?3BAIE7p#xavfFCZ&jZiZ8mkb#UBnvS{Q5^&n ztO}@7G**C#ln?wPiv!r`Hyogf!T=fcB>!2AM1?i~&_*;iqK)GGFZvPb7OkYQ-6d6& z0a{OcUJDGspH@OP(x4B?gW62rt&CI|wN;OPj8*k5Lr*b|%Fq(=Ur#PX{HOPcy(v#2 z5oip`G|`R!(XPYX5HdyvHJAb{`ebJVgFYhhRL9aGYKjMKT|^Bb1SooT>0@UB*$)Tt zHic0{lgFYN3z~T933^2eY^<#Wk;>(K9xc3uTVMnV0^ct=LK|GNL*)69)+(kKq$9AL zARW)H|HA@25+vG+A6d$&fERzl0`(TZee2R7x$T~5gkmJec=!XJN6k$%me-s`jAKeQ}Qfmq^{`GP7jZZ8Xt2|wS#4gG97WzxFc+kV(^EU3Lrg-L-a7eD(8WF@P!>fC@8`x zFQZln2jX3bWfUD8LVX390vn0}XEf|^_VL7ZfBNn=)2v+S2MVgP&rM|an(|jP5otjj z#Ml}Cj~79v{fqzAe1m0C_(Wh5UO$rgT%tF$!6Wmak5ZfO*C+_gf$30G)CqnCF-WP- z;80{93sV~L*iX98`F|1^Rvf}Wf;@x5%gb_+VI4-=){L`BHP(?-7#g*N$7a|D^cfB} zK*1ULCw+7&D1Lr{7nBh>`H6jcdMq&}9N6Zw4=ILxsV^(g?ZNmoeY4P7p0@#>S2Vku zW)`Cf1r;h7qxMGhAQ(J^4@isrZ{B@$RTk9faKMmAe$_U@4V`6dt06*t13Qa&*52UXlHWYtJwSS&=fBIaF|v zDnCsxMyU|s+Fhap*#kOxTyP_ejl)jV(Vk#F-+W|q#!#3<_W;Fj++W08WN=9WymAgK72Ld8;c3VcLH#OQzDAw`P^}<*fPl@`jnhAW9g^`s+1Z{vWDY^%Xt(5_6 zjdMR@q@LHoOuEkneR3q3dbk<5?dG?oq+MbHVxxDMB}5mRO?M~*6EF>E(;2{NtZ8)l zfXNXpDn(a>XHaFe(S6GwDj=L9!W)Ud74J3H=&K7r$bcWa8Za%U)A4 z7It(Rxm{rtl+iMdGfyW7*l8526pw_PW?a6U5^@9{0YG^(R8SYx%WTVNE6XrN3qU2; z8R+TW)0;G2Tj(vi+LEE<*YBx1(M(x~D!6)D7AnIpah4q-#iJ==| z_FH&mep{t?jYtsd3!11@Vr?qTadC$kyc{8}MC%EPXFcjrIH0GC@yK9oooavXc`qaG zQ7*!4nU2EHhCB)N6sNLR^5&}WPjq8eJEc{Nad*>SZ0MRx#$dK42jo0N2R#Z=y=EE< zE`??OMs$x^#+c|sR(RgM&Vz72AxF0!${75ehH57MXo8k7A_F+ z122J$xDB6d2nEhZyk{XwJo=4|IGNn!kB@iBpe#mHvf`6y91Nn@4PI`N3O{^D*#n@` zf_OGltaLIk%V_gT_w|_yb2Ae^o&Q@OQ58Uvr#C+ad=oJ^nb8FAT~CP<936$}xjy*YEXWslI|x{sQC&V*B<_-F&)pDM5%C4!KtRpfBT`*3347P{s+l zL8S*9sHN}p>I|7tf6ZPAZ|tACbci4%E`S`9q@B+HWOkeGkgq_FuQhhz@VS%U`yAi5 z@q3`w)c&pGS01j7u7Qvrs$Hvp{z2;;iy*}HhFq;q`yuUboooHIAe19HsZNLUzdto= zf=du$FG228CpT;5tZ_Fs;X4h#Ds0iguLd6a;h*>%4tYks=Lc_kqJCfOr`!a{@%3)| zwJq&NWnh0FLavcK^*86YO`G+^{zW+uOJY;)0ySKKhQ2l{>W zZ&trCW%=5dq3=`3ahA4arwnTMQhmtfAy;c;>#;iN&|`-o{|LE%qo1q)+PT^X?_p^V ziw zIs6{B&O6dPrKz)~Ae1J1t3O)#>rant>--38EekoeS#IL-_s7|mqMz2U(mZze{vGSS zZzc#8uh9JFlRD12ym(as{6+goYQCmu{;n%Yse(`eJ{7vQ?7vv?Rb9Rxs>r_BRxQ>I z6*G%IBU2#9wd*S#d30cR_@`7A$W`0TYI&{qxrykDONShvzI4j9COeydhxJk*cWghk zX6A@p+j61*T`sS>v$|o;cF7ai#JvR{93^ z5njwOckAl>(9k_t4|4?OoVX)Np1HUP`z3$Zm5x`Ku;b>PTc9r4v&~g?)!c-JxpN?s zKdQNw*{7a*zvluhMf&TF(7qo3>lX#UyA0tGFIVh3<;-uimpCWn>azRNxM>wxWzNRyt)SlnIY!C7Ty;;=hAp9w z@I9cOoi=B}lin<>2b+ZlwHJ4m@zhH`hy83KIq&HuZ|oR-0(|5DD(3I0G9;I+orm%1 zXo($P`eLWE2`LkykNlZ9zFq2t1-8>G@VNwX`SJ5!vUx)vT?4*;$d8Qw?$yE`gH}Bb z|BrzD^u&j|-D>m0S?o8K_ETfhyw=0+^x3ika#zR;rym}3A!qHaVbJ#+JFYSe1-Y6 zzqpOZe|~#Q&R*bIj_gU;WV7|kDvJLJ_z?eUY^iHKEwnoZdj!%Kx7FpS)OTw<_+9n_ zST!j2CtiZ41$6Hcg-j}JnhqcpM)m-vabogp;NHzO*lM#)`-?;U@F;DVgEAQn#TR?|H4wYA;%sNyLCTP zbOHM*YlobCV2hi*X-SeWl=>SVel^QpweHcY;3Gt#^5M^$KI2b~$KGRlK(2bM>(HVG z2TnDDePr*MW4Cg$N9_CcAo>ZpA);K{r8X)A(uUQdv}HPKb#0c-`^o8p1WGO z!PfPcc~DL9u;J=AYp!>4{c-|B7eKCXt;2+OW;E=8{2x;Ux%IUh*Wdcv2NvL6rV8XL x*ZbDK*(1B72$}GSyLsl7k(~y`Az#N|flVL&Zh81p!z-iU>++BV#C6pw{{gU>e literal 0 HcmV?d00001 diff --git a/Mi_Lua/luci/i18n/firewall.pt.lmo b/Mi_Lua/luci/i18n/firewall.pt.lmo new file mode 100644 index 0000000000000000000000000000000000000000..a62e0f8769c5f3206f671f0a9f9dd7c4c77d7789 GIT binary patch literal 724 zcmZ`#O-K}B82$uxJhZzhYm2eMR|y)fk`P3P4yFr%m>bg`+!Fkp`DUG%=3BlQ+s!%z z5+NZ3MG>XA2#L^-bgIMJ&zw34ViAQ$XdNYE= zZJkT*%?he>ypC(yA|a&9%5?XZnRH8$VUlK;CS2}N(>~+nEmIc`AtWJTE0&~c|Is$X z5<=gq6VJI^+6*yq+m%dbWj#&X_X$70*z<2}ud23n2`Rben!+YTW!^ocmgV+#cKI_3 zX{Pz<|JwiTQi}Um_ff-?Jj*PLYSLtMRM^a+ju?`hf)ocdn1*%HOLBa00!B4~U4(^e z2x?d(Ih;>WS9*Dar$syVN%?i~N2=*EN%Z3F2$B3$(V(O*@9DW(aSS2zKy{%1?Y8?( z^{e0sARMf1lD`gZj)3n68X9`)9xRSco*<-R7+Q&*y*{}2>-cAI|52i2$ph~;EBApf z0g=XNGFQ@%e7$QTHNWvD-wJ*O2sD3*Y#iS4qyYX22(@;^(jP-RpMkr8(lHc%y)YV| z1djsSPp+@6wLE_6`)YvjfEHZ2w)^Hm@M$18?0h&^s;@%_E-q)^AD{a%fO!X@t1i9x zHr-qqxee{-C#M!xGNu0Cp+k489v*4Q{(K344!Y*~Ord?et}p?99;jWMir<-ACpX~F UKvyr{>Py64sg2+ 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 ICiIN{Uk~vl#{d8T literal 0 HcmV?d00001 diff --git a/Mi_Lua/luci/i18n/firewall.uk.lmo b/Mi_Lua/luci/i18n/firewall.uk.lmo new file mode 100644 index 0000000000000000000000000000000000000000..083de3aa61b007ad31c861173cca68d7968a5303 GIT binary patch literal 10800 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 ZvO#=-OSAn0ikD1F1F}tkm=CCK1^^6=0oMQk literal 0 HcmV?d00001 diff --git a/Mi_Lua/luci/i18n/firewall.zh-cn.lmo b/Mi_Lua/luci/i18n/firewall.zh-cn.lmo new file mode 100644 index 0000000000000000000000000000000000000000..ffa1078e3c1125ed6b1f6cbe6b2029c0979e8530 GIT binary patch literal 5968 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 M^%>TYCyvSg1$*MyApigX literal 0 HcmV?d00001 diff --git a/Mi_Lua/luci/i18n/firewall.zh-tw.lmo b/Mi_Lua/luci/i18n/firewall.zh-tw.lmo new file mode 100644 index 0000000000000000000000000000000000000000..ffa1078e3c1125ed6b1f6cbe6b2029c0979e8530 GIT binary patch literal 5968 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 M^%>TYCyvSg1$*MyApigX literal 0 HcmV?d00001 diff --git a/Mi_Lua/luci/init.lua b/Mi_Lua/luci/init.lua new file mode 100644 index 0000000..9689fcf --- /dev/null +++ b/Mi_Lua/luci/init.lua @@ -0,0 +1,39 @@ +--[[ +LuCI - Lua Configuration Interface + +Description: +Main class + +FileId: +$Id: init.lua 7365 2011-08-13 09:52:50Z jow $ + +License: +Copyright 2008 Steven Barth + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +]]-- + +local require = require + +-- Make sure that bitlib is loaded +if not _G.bit then + _G.bit = require "bit" +end + +module "luci" + +local v = require "luci.version" + +__version__ = v.luciversion or "trunk" +__appname__ = v.luciname or "LuCI" diff --git a/Mi_Lua/luci/ip.lua b/Mi_Lua/luci/ip.lua new file mode 100644 index 0000000..07c177a --- /dev/null +++ b/Mi_Lua/luci/ip.lua @@ -0,0 +1,684 @@ +--[[ + +LuCI ip calculation libarary +(c) 2008 Jo-Philipp Wich +(c) 2008 Steven Barth + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +$Id: ip.lua 8142 2012-01-01 15:51:37Z jow $ + +]]-- + +--- LuCI IP calculation library. +module( "luci.ip", package.seeall ) + +require "nixio" +local bit = nixio.bit +local util = require "luci.util" + +--- Boolean; true if system is little endian +LITTLE_ENDIAN = not util.bigendian() + +--- Boolean; true if system is big endian +BIG_ENDIAN = not LITTLE_ENDIAN + +--- Specifier for IPv4 address family +FAMILY_INET4 = 0x04 + +--- Specifier for IPv6 address family +FAMILY_INET6 = 0x06 + + +local function __bless(x) + return setmetatable( x, { + __index = luci.ip.cidr, + __add = luci.ip.cidr.add, + __sub = luci.ip.cidr.sub, + __lt = luci.ip.cidr.lower, + __eq = luci.ip.cidr.equal, + __le = + function(...) + return luci.ip.cidr.equal(...) or luci.ip.cidr.lower(...) + end + } ) +end + +local function __array16( x, family ) + local list + + if type(x) == "number" then + list = { bit.rshift(x, 16), bit.band(x, 0xFFFF) } + + elseif type(x) == "string" then + if x:find(":") then x = IPv6(x) else x = IPv4(x) end + if x then + assert( x[1] == family, "Can't mix IPv4 and IPv6 addresses" ) + list = { unpack(x[2]) } + end + + elseif type(x) == "table" and type(x[2]) == "table" then + assert( x[1] == family, "Can't mix IPv4 and IPv6 addresses" ) + list = { unpack(x[2]) } + + elseif type(x) == "table" then + list = { unpack(x) } + end + + assert( list, "Invalid operand" ) + + return list +end + +local function __mask16(bits) + return bit.lshift( bit.rshift( 0xFFFF, 16 - bits % 16 ), 16 - bits % 16 ) +end + +local function __not16(bits) + return bit.band( bit.bnot( __mask16(bits) ), 0xFFFF ) +end + +local function __maxlen(family) + return ( family == FAMILY_INET4 ) and 32 or 128 +end + +local function __sublen(family) + return ( family == FAMILY_INET4 ) and 30 or 127 +end + + +--- Convert given short value to network byte order on little endian hosts +-- @param x Unsigned integer value between 0x0000 and 0xFFFF +-- @return Byte-swapped value +-- @see htonl +-- @see ntohs +function htons(x) + if LITTLE_ENDIAN then + return bit.bor( + bit.rshift( x, 8 ), + bit.band( bit.lshift( x, 8 ), 0xFF00 ) + ) + else + return x + end +end + +--- Convert given long value to network byte order on little endian hosts +-- @param x Unsigned integer value between 0x00000000 and 0xFFFFFFFF +-- @return Byte-swapped value +-- @see htons +-- @see ntohl +function htonl(x) + if LITTLE_ENDIAN then + return bit.bor( + bit.lshift( htons( bit.band( x, 0xFFFF ) ), 16 ), + htons( bit.rshift( x, 16 ) ) + ) + else + return x + end +end + +--- Convert given short value to host byte order on little endian hosts +-- @class function +-- @name ntohs +-- @param x Unsigned integer value between 0x0000 and 0xFFFF +-- @return Byte-swapped value +-- @see htonl +-- @see ntohs +ntohs = htons + +--- Convert given short value to host byte order on little endian hosts +-- @class function +-- @name ntohl +-- @param x Unsigned integer value between 0x00000000 and 0xFFFFFFFF +-- @return Byte-swapped value +-- @see htons +-- @see ntohl +ntohl = htonl + + +--- Parse given IPv4 address in dotted quad or CIDR notation. If an optional +-- netmask is given as second argument and the IP address is encoded in CIDR +-- notation then the netmask parameter takes precedence. If neither a CIDR +-- encoded prefix nor a netmask parameter is given, then a prefix length of +-- 32 bit is assumed. +-- @param address IPv4 address in dotted quad or CIDR notation +-- @param netmask IPv4 netmask in dotted quad notation (optional) +-- @return luci.ip.cidr instance or nil if given address was invalid +-- @see IPv6 +-- @see Hex +function IPv4(address, netmask) + address = address or "0.0.0.0/0" + + local obj = __bless({ FAMILY_INET4 }) + + local data = {} + local prefix = address:match("/(.+)") + address = address:gsub("/.+","") + address = address:gsub("^%[(.*)%]$", "%1"):upper():gsub("^::FFFF:", "") + + if netmask then + prefix = obj:prefix(netmask) + elseif prefix then + prefix = tonumber(prefix) + if not prefix or prefix < 0 or prefix > 32 then return nil end + else + prefix = 32 + end + + local b1, b2, b3, b4 = address:match("^(%d+)%.(%d+)%.(%d+)%.(%d+)$") + + b1 = tonumber(b1) + b2 = tonumber(b2) + b3 = tonumber(b3) + b4 = tonumber(b4) + + if b1 and b1 <= 255 and + b2 and b2 <= 255 and + b3 and b3 <= 255 and + b4 and b4 <= 255 and + prefix + then + table.insert(obj, { b1 * 256 + b2, b3 * 256 + b4 }) + table.insert(obj, prefix) + return obj + end +end + +--- Parse given IPv6 address in full, compressed, mixed or CIDR notation. +-- If an optional netmask is given as second argument and the IP address is +-- encoded in CIDR notation then the netmask parameter takes precedence. +-- If neither a CIDR encoded prefix nor a netmask parameter is given, then a +-- prefix length of 128 bit is assumed. +-- @param address IPv6 address in full/compressed/mixed or CIDR notation +-- @param netmask IPv6 netmask in full/compressed/mixed notation (optional) +-- @return luci.ip.cidr instance or nil if given address was invalid +-- @see IPv4 +-- @see Hex +function IPv6(address, netmask) + address = address or "::/0" + + local obj = __bless({ FAMILY_INET6 }) + + local data = {} + local prefix = address:match("/(.+)") + address = address:gsub("/.+","") + address = address:gsub("^%[(.*)%]$", "%1") + + if netmask then + prefix = obj:prefix(netmask) + elseif prefix then + prefix = tonumber(prefix) + if not prefix or prefix < 0 or prefix > 128 then return nil end + else + prefix = 128 + end + + local borderl = address:sub(1, 1) == ":" and 2 or 1 + local borderh, zeroh, chunk, block, i + + if #address > 45 then return nil end + + repeat + borderh = address:find(":", borderl, true) + if not borderh then break end + + block = tonumber(address:sub(borderl, borderh - 1), 16) + if block and block <= 0xFFFF then + data[#data+1] = block + else + if zeroh or borderh - borderl > 1 then return nil end + zeroh = #data + 1 + end + + borderl = borderh + 1 + until #data == 7 + + chunk = address:sub(borderl) + if #chunk > 0 and #chunk <= 4 then + block = tonumber(chunk, 16) + if not block or block > 0xFFFF then return nil end + + data[#data+1] = block + elseif #chunk > 4 then + if #data == 7 or #chunk > 15 then return nil end + borderl = 1 + for i=1, 4 do + borderh = chunk:find(".", borderl, true) + if not borderh and i < 4 then return nil end + borderh = borderh and borderh - 1 + + block = tonumber(chunk:sub(borderl, borderh)) + if not block or block > 255 then return nil end + + if i == 1 or i == 3 then + data[#data+1] = block * 256 + else + data[#data] = data[#data] + block + end + + borderl = borderh and borderh + 2 + end + end + + if zeroh then + if #data == 8 then return nil end + while #data < 8 do + table.insert(data, zeroh, 0) + end + end + + if #data == 8 and prefix then + table.insert(obj, data) + table.insert(obj, prefix) + return obj + end +end + +--- Transform given hex-encoded value to luci.ip.cidr instance of specified +-- address family. +-- @param hex String containing hex encoded value +-- @param prefix Prefix length of CIDR instance (optional, default is 32/128) +-- @param family Address family, either luci.ip.FAMILY_INET4 or FAMILY_INET6 +-- @param swap Bool indicating whether to swap byteorder on low endian host +-- @return luci.ip.cidr instance or nil if given value was invalid +-- @see IPv4 +-- @see IPv6 +function Hex( hex, prefix, family, swap ) + family = ( family ~= nil ) and family or FAMILY_INET4 + swap = ( swap == nil ) and true or swap + prefix = prefix or __maxlen(family) + + local len = __maxlen(family) + local tmp = "" + local data = { } + local i + + for i = 1, (len/4) - #hex do tmp = tmp .. '0' end + + if swap and LITTLE_ENDIAN then + for i = #hex, 1, -2 do tmp = tmp .. hex:sub( i - 1, i ) end + else + tmp = tmp .. hex + end + + hex = tmp + + for i = 1, ( len / 4 ), 4 do + local n = tonumber( hex:sub( i, i+3 ), 16 ) + if n then + data[#data+1] = n + else + return nil + end + end + + return __bless({ family, data, prefix }) +end + + +--- LuCI IP Library / CIDR instances +-- @class module +-- @cstyle instance +-- @name luci.ip.cidr +cidr = util.class() + +--- Test whether the instance is a IPv4 address. +-- @return Boolean indicating a IPv4 address type +-- @see cidr.is6 +function cidr.is4( self ) + return self[1] == FAMILY_INET4 +end + +--- Test whether this instance is an IPv4 RFC1918 private address +-- @return Boolean indicating whether this instance is an RFC1918 address +function cidr.is4rfc1918( self ) + if self[1] == FAMILY_INET4 then + return ((self[2][1] >= 0x0A00) and (self[2][1] <= 0x0AFF)) or + ((self[2][1] >= 0xAC10) and (self[2][1] <= 0xAC1F)) or + (self[2][1] == 0xC0A8) + end + return false +end + +--- Test whether this instance is an IPv4 link-local address (Zeroconf) +-- @return Boolean indicating whether this instance is IPv4 link-local +function cidr.is4linklocal( self ) + if self[1] == FAMILY_INET4 then + return (self[2][1] == 0xA9FE) + end + return false +end + +--- Test whether the instance is a IPv6 address. +-- @return Boolean indicating a IPv6 address type +-- @see cidr.is4 +function cidr.is6( self ) + return self[1] == FAMILY_INET6 +end + +--- Test whether this instance is an IPv6 link-local address +-- @return Boolean indicating whether this instance is IPv6 link-local +function cidr.is6linklocal( self ) + if self[1] == FAMILY_INET6 then + return (self[2][1] >= 0xFE80) and (self[2][1] <= 0xFEBF) + end + return false +end + +--- Return a corresponding string representation of the instance. +-- If the prefix length is lower then the maximum possible prefix length for the +-- corresponding address type then the address is returned in CIDR notation, +-- otherwise the prefix will be left out. +function cidr.string( self ) + local str + if self:is4() then + str = string.format( + "%d.%d.%d.%d", + bit.rshift(self[2][1], 8), bit.band(self[2][1], 0xFF), + bit.rshift(self[2][2], 8), bit.band(self[2][2], 0xFF) + ) + if self[3] < 32 then + str = str .. "/" .. self[3] + end + elseif self:is6() then + str = string.format( "%X:%X:%X:%X:%X:%X:%X:%X", unpack(self[2]) ) + if self[3] < 128 then + str = str .. "/" .. self[3] + end + end + return str +end + +--- Test whether the value of the instance is lower then the given address. +-- This function will throw an exception if the given address has a different +-- family than this instance. +-- @param addr A luci.ip.cidr instance to compare against +-- @return Boolean indicating whether this instance is lower +-- @see cidr.higher +-- @see cidr.equal +function cidr.lower( self, addr ) + assert( self[1] == addr[1], "Can't compare IPv4 and IPv6 addresses" ) + local i + for i = 1, #self[2] do + if self[2][i] ~= addr[2][i] then + return self[2][i] < addr[2][i] + end + end + return false +end + +--- Test whether the value of the instance is higher then the given address. +-- This function will throw an exception if the given address has a different +-- family than this instance. +-- @param addr A luci.ip.cidr instance to compare against +-- @return Boolean indicating whether this instance is higher +-- @see cidr.lower +-- @see cidr.equal +function cidr.higher( self, addr ) + assert( self[1] == addr[1], "Can't compare IPv4 and IPv6 addresses" ) + local i + for i = 1, #self[2] do + if self[2][i] ~= addr[2][i] then + return self[2][i] > addr[2][i] + end + end + return false +end + +--- Test whether the value of the instance is equal to the given address. +-- This function will throw an exception if the given address is a different +-- family than this instance. +-- @param addr A luci.ip.cidr instance to compare against +-- @return Boolean indicating whether this instance is equal +-- @see cidr.lower +-- @see cidr.higher +function cidr.equal( self, addr ) + assert( self[1] == addr[1], "Can't compare IPv4 and IPv6 addresses" ) + local i + for i = 1, #self[2] do + if self[2][i] ~= addr[2][i] then + return false + end + end + return true +end + +--- Return the prefix length of this CIDR instance. +-- @param mask Override instance prefix with given netmask (optional) +-- @return Prefix length in bit +function cidr.prefix( self, mask ) + local prefix = self[3] + + if mask then + prefix = 0 + + local stop = false + local obj = type(mask) ~= "table" + and ( self:is4() and IPv4(mask) or IPv6(mask) ) or mask + + if not obj then return nil end + + local _, word + for _, word in ipairs(obj[2]) do + if word == 0xFFFF then + prefix = prefix + 16 + else + local bitmask = bit.lshift(1, 15) + while bit.band(word, bitmask) == bitmask do + prefix = prefix + 1 + bitmask = bit.lshift(1, 15 - (prefix % 16)) + end + + break + end + end + end + + return prefix +end + +--- Return a corresponding CIDR representing the network address of this +-- instance. +-- @param bits Override prefix length of this instance (optional) +-- @return CIDR instance containing the network address +-- @see cidr.host +-- @see cidr.broadcast +-- @see cidr.mask +function cidr.network( self, bits ) + local data = { } + bits = bits or self[3] + + local i + for i = 1, math.floor( bits / 16 ) do + data[#data+1] = self[2][i] + end + + if #data < #self[2] then + data[#data+1] = bit.band( self[2][1+#data], __mask16(bits) ) + + for i = #data + 1, #self[2] do + data[#data+1] = 0 + end + end + + return __bless({ self[1], data, __maxlen(self[1]) }) +end + +--- Return a corresponding CIDR representing the host address of this +-- instance. This is intended to extract the host address from larger subnet. +-- @return CIDR instance containing the network address +-- @see cidr.network +-- @see cidr.broadcast +-- @see cidr.mask +function cidr.host( self ) + return __bless({ self[1], self[2], __maxlen(self[1]) }) +end + +--- Return a corresponding CIDR representing the netmask of this instance. +-- @param bits Override prefix length of this instance (optional) +-- @return CIDR instance containing the netmask +-- @see cidr.network +-- @see cidr.host +-- @see cidr.broadcast +function cidr.mask( self, bits ) + local data = { } + bits = bits or self[3] + + for i = 1, math.floor( bits / 16 ) do + data[#data+1] = 0xFFFF + end + + if #data < #self[2] then + data[#data+1] = __mask16(bits) + + for i = #data + 1, #self[2] do + data[#data+1] = 0 + end + end + + return __bless({ self[1], data, __maxlen(self[1]) }) +end + +--- Return CIDR containing the broadcast address of this instance. +-- @return CIDR instance containing the netmask, always nil for IPv6 +-- @see cidr.network +-- @see cidr.host +-- @see cidr.mask +function cidr.broadcast( self ) + -- IPv6 has no broadcast addresses (XXX: assert() instead?) + if self[1] == FAMILY_INET4 then + local data = { unpack(self[2]) } + local offset = math.floor( self[3] / 16 ) + 1 + + if offset <= #data then + data[offset] = bit.bor( data[offset], __not16(self[3]) ) + for i = offset + 1, #data do data[i] = 0xFFFF end + + return __bless({ self[1], data, __maxlen(self[1]) }) + end + end +end + +--- Test whether this instance fully contains the given CIDR instance. +-- @param addr CIDR instance to test against +-- @return Boolean indicating whether this instance contains the given CIDR +function cidr.contains( self, addr ) + assert( self[1] == addr[1], "Can't compare IPv4 and IPv6 addresses" ) + + if self:prefix() <= addr:prefix() then + return self:network() == addr:network(self:prefix()) + end + + return false +end + +--- Add specified amount of hosts to this instance. +-- @param amount Number of hosts to add to this instance +-- @param inplace Boolen indicating whether to alter values inplace (optional) +-- @return CIDR representing the new address or nil on overflow error +-- @see cidr.sub +function cidr.add( self, amount, inplace ) + local pos + local data = { unpack(self[2]) } + local shorts = __array16( amount, self[1] ) + + for pos = #data, 1, -1 do + local add = ( #shorts > 0 ) and table.remove( shorts, #shorts ) or 0 + if ( data[pos] + add ) > 0xFFFF then + data[pos] = ( data[pos] + add ) % 0xFFFF + if pos > 1 then + data[pos-1] = data[pos-1] + ( add - data[pos] ) + else + return nil + end + else + data[pos] = data[pos] + add + end + end + + if inplace then + self[2] = data + return self + else + return __bless({ self[1], data, self[3] }) + end +end + +--- Substract specified amount of hosts from this instance. +-- @param amount Number of hosts to substract from this instance +-- @param inplace Boolen indicating whether to alter values inplace (optional) +-- @return CIDR representing the new address or nil on underflow error +-- @see cidr.add +function cidr.sub( self, amount, inplace ) + local pos + local data = { unpack(self[2]) } + local shorts = __array16( amount, self[1] ) + + for pos = #data, 1, -1 do + local sub = ( #shorts > 0 ) and table.remove( shorts, #shorts ) or 0 + if ( data[pos] - sub ) < 0 then + data[pos] = ( sub - data[pos] ) % 0xFFFF + if pos > 1 then + data[pos-1] = data[pos-1] - ( sub + data[pos] ) + else + return nil + end + else + data[pos] = data[pos] - sub + end + end + + if inplace then + self[2] = data + return self + else + return __bless({ self[1], data, self[3] }) + end +end + +--- Return CIDR containing the lowest available host address within this subnet. +-- @return CIDR containing the host address, nil if subnet is too small +-- @see cidr.maxhost +function cidr.minhost( self ) + if self[3] <= __sublen(self[1]) then + -- 1st is Network Address in IPv4 and Subnet-Router Anycast Adresse in IPv6 + return self:network():add(1, true) + end +end + +--- Return CIDR containing the highest available host address within the subnet. +-- @return CIDR containing the host address, nil if subnet is too small +-- @see cidr.minhost +function cidr.maxhost( self ) + if self[3] <= __sublen(self[1]) then + local i + local data = { unpack(self[2]) } + local offset = math.floor( self[3] / 16 ) + 1 + + data[offset] = bit.bor( data[offset], __not16(self[3]) ) + for i = offset + 1, #data do data[i] = 0xFFFF end + data = __bless({ self[1], data, __maxlen(self[1]) }) + + -- Last address in reserved for Broadcast Address in IPv4 + if data[1] == FAMILY_INET4 then data:sub(1, true) end + + return data + end +end + +function iptonl( ip ) + local a,b,c,d = ip:match("^(%d+).(%d+).(%d+).(%d+)") + local ipnl = bit.lshift(a, 24) + bit.lshift(b, 16) + bit.lshift(c, 8) + d + return ipnl +end + +function ipnot ( ip ) + local ipnl = iptonl(ip) + return bit.band(bit.bnot(ipnl),0xFFFFFFFF) +end diff --git a/Mi_Lua/luci/json.lua b/Mi_Lua/luci/json.lua new file mode 100644 index 0000000..0218a1f --- /dev/null +++ b/Mi_Lua/luci/json.lua @@ -0,0 +1,566 @@ +--[[ +LuCI - Lua Configuration Interface + +Copyright 2008 Steven Barth +Copyright 2008 Jo-Philipp Wich + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +$Id: json.lua 6988 2011-04-17 11:39:17Z jow $ + +Decoder: + Info: + null will be decoded to luci.json.null if first parameter of Decoder() is true + + Example: + decoder = luci.json.Decoder() + luci.ltn12.pump.all(luci.ltn12.source.string("decodableJSON"), decoder:sink()) + luci.util.dumptable(decoder:get()) + + Known issues: + does not support unicode conversion \uXXYY with XX != 00 will be ignored + + +Encoder: + Info: + Accepts numbers, strings, nil, booleans as they are + Accepts luci.json.null as replacement for nil + Accepts full associative and full numerically indexed tables + Mixed tables will loose their associative values during conversion + Iterator functions will be encoded as an array of their return values + Non-iterator functions will probably corrupt the encoder + + Example: + encoder = luci.json.Encoder(encodableData) + luci.ltn12.pump.all(encoder:source(), luci.ltn12.sink.file(io.open("someFile", w))) +]]-- + +local util = require "luci.util" +local table = require "table" +local string = require "string" +local coroutine = require "coroutine" + +local assert = assert +local tonumber = tonumber +local tostring = tostring +local error = error +local type = type +local pairs = pairs +local ipairs = ipairs +local next = next +local pcall = pcall + +local getmetatable = getmetatable + +--- LuCI JSON-Library +-- @cstyle instance +module "luci.json" + + +--- Directly decode a JSON string +-- @param json JSON-String +-- @return Lua object +function decode(json, ...) + local a = ActiveDecoder(function() return nil end, ...) + a.chunk = json + local s, obj = pcall(a.get, a) + return s and obj or nil +end + + +--- Direcly encode a Lua object into a JSON string. +-- @param obj Lua Object +-- @return JSON string +function encode(obj, ...) + local out = {} + local e = Encoder(obj, 1, ...):source() + local chnk, err + repeat + chnk, err = e() + out[#out+1] = chnk + until not chnk + return not err and table.concat(out) or nil +end + + +--- Null replacement function +-- @return null +function null() + return null +end + +--- Create a new JSON-Encoder. +-- @class function +-- @name Encoder +-- @param data Lua-Object to be encoded. +-- @param buffersize Blocksize of returned data source. +-- @param fastescape Use non-standard escaping (don't escape control chars) +-- @return JSON-Encoder +Encoder = util.class() + +function Encoder.__init__(self, data, buffersize, fastescape) + self.data = data + self.buffersize = buffersize or 512 + self.buffer = "" + self.fastescape = fastescape + + getmetatable(self).__call = Encoder.source +end + +--- Create an LTN12 source providing the encoded JSON-Data. +-- @return LTN12 source +function Encoder.source(self) + local source = coroutine.create(self.dispatch) + return function() + local res, data = coroutine.resume(source, self, self.data, true) + if res then + return data + else + return nil, data + end + end +end + +function Encoder.dispatch(self, data, start) + local parser = self.parsers[type(data)] + + parser(self, data) + + if start then + if #self.buffer > 0 then + coroutine.yield(self.buffer) + end + + coroutine.yield() + end +end + +function Encoder.put(self, chunk) + if self.buffersize < 2 then + coroutine.yield(chunk) + else + if #self.buffer + #chunk > self.buffersize then + local written = 0 + local fbuffer = self.buffersize - #self.buffer + + coroutine.yield(self.buffer .. chunk:sub(written + 1, fbuffer)) + written = fbuffer + + while #chunk - written > self.buffersize do + fbuffer = written + self.buffersize + coroutine.yield(chunk:sub(written + 1, fbuffer)) + written = fbuffer + end + + self.buffer = chunk:sub(written + 1) + else + self.buffer = self.buffer .. chunk + end + end +end + +function Encoder.parse_nil(self) + self:put("null") +end + +function Encoder.parse_bool(self, obj) + self:put(obj and "true" or "false") +end + +function Encoder.parse_number(self, obj) + self:put(tostring(obj)) +end + +function Encoder.parse_string(self, obj) + if self.fastescape then + self:put('"' .. obj:gsub('\\', '\\\\'):gsub('"', '\\"') .. '"') + else + self:put('"' .. + obj:gsub('[%c\\"]', + function(char) + return '\\u00%02x' % char:byte() + end + ) + .. '"') + end +end + +function Encoder.parse_iter(self, obj) + if obj == null then + return self:put("null") + end + + if type(obj) == "table" and (#obj == 0 and next(obj)) then + self:put("{") + local first = true + + for key, entry in pairs(obj) do + first = first or self:put(",") + first = first and false + self:parse_string(tostring(key)) + self:put(":") + self:dispatch(entry) + end + + self:put("}") + else + self:put("[") + local first = true + + if type(obj) == "table" then + for i=1, #obj do + first = first or self:put(",") + first = first and nil + self:dispatch(obj[i]) + end + else + for entry in obj do + first = first or self:put(",") + first = first and nil + self:dispatch(entry) + end + end + + self:put("]") + end +end + +Encoder.parsers = { + ['nil'] = Encoder.parse_nil, + ['table'] = Encoder.parse_iter, + ['number'] = Encoder.parse_number, + ['string'] = Encoder.parse_string, + ['boolean'] = Encoder.parse_bool, + ['function'] = Encoder.parse_iter +} + + +--- Create a new JSON-Decoder. +-- @class function +-- @name Decoder +-- @param customnull Use luci.json.null instead of nil for decoding null +-- @return JSON-Decoder +Decoder = util.class() + +function Decoder.__init__(self, customnull) + self.cnull = customnull + getmetatable(self).__call = Decoder.sink +end + +--- Create an LTN12 sink from the decoder object which accepts the JSON-Data. +-- @return LTN12 sink +function Decoder.sink(self) + local sink = coroutine.create(self.dispatch) + return function(...) + return coroutine.resume(sink, self, ...) + end +end + + +--- Get the decoded data packets after the rawdata has been sent to the sink. +-- @return Decoded data +function Decoder.get(self) + return self.data +end + +function Decoder.dispatch(self, chunk, src_err, strict) + local robject, object + local oset = false + + while chunk do + while chunk and #chunk < 1 do + chunk = self:fetch() + end + + assert(not strict or chunk, "Unexpected EOS") + if not chunk then break end + + local char = chunk:sub(1, 1) + local parser = self.parsers[char] + or (char:match("%s") and self.parse_space) + or (char:match("[0-9-]") and self.parse_number) + or error("Unexpected char '%s'" % char) + + chunk, robject = parser(self, chunk) + + if parser ~= self.parse_space then + assert(not oset, "Scope violation: Too many objects") + object = robject + oset = true + + if strict then + return chunk, object + end + end + end + + assert(not src_err, src_err) + assert(oset, "Unexpected EOS") + + self.data = object +end + + +function Decoder.fetch(self) + local tself, chunk, src_err = coroutine.yield() + assert(chunk or not src_err, src_err) + return chunk +end + + +function Decoder.fetch_atleast(self, chunk, bytes) + while #chunk < bytes do + local nchunk = self:fetch() + assert(nchunk, "Unexpected EOS") + chunk = chunk .. nchunk + end + + return chunk +end + + +function Decoder.fetch_until(self, chunk, pattern) + local start = chunk:find(pattern) + + while not start do + local nchunk = self:fetch() + assert(nchunk, "Unexpected EOS") + chunk = chunk .. nchunk + start = chunk:find(pattern) + end + + return chunk, start +end + + +function Decoder.parse_space(self, chunk) + local start = chunk:find("[^%s]") + + while not start do + chunk = self:fetch() + if not chunk then + return nil + end + start = chunk:find("[^%s]") + end + + return chunk:sub(start) +end + + +function Decoder.parse_literal(self, chunk, literal, value) + chunk = self:fetch_atleast(chunk, #literal) + assert(chunk:sub(1, #literal) == literal, "Invalid character sequence") + return chunk:sub(#literal + 1), value +end + + +function Decoder.parse_null(self, chunk) + return self:parse_literal(chunk, "null", self.cnull and null) +end + + +function Decoder.parse_true(self, chunk) + return self:parse_literal(chunk, "true", true) +end + + +function Decoder.parse_false(self, chunk) + return self:parse_literal(chunk, "false", false) +end + + +function Decoder.parse_number(self, chunk) + local chunk, start = self:fetch_until(chunk, "[^0-9eE.+-]") + local number = tonumber(chunk:sub(1, start - 1)) + assert(number, "Invalid number specification") + return chunk:sub(start), number +end + + +function Decoder.parse_string(self, chunk) + local str = "" + local object = nil + assert(chunk:sub(1, 1) == '"', 'Expected "') + chunk = chunk:sub(2) + + while true do + local spos = chunk:find('[\\"]') + if spos then + str = str .. chunk:sub(1, spos - 1) + + local char = chunk:sub(spos, spos) + if char == '"' then -- String end + chunk = chunk:sub(spos + 1) + break + elseif char == "\\" then -- Escape sequence + chunk, object = self:parse_escape(chunk:sub(spos)) + str = str .. object + end + else + str = str .. chunk + chunk = self:fetch() + assert(chunk, "Unexpected EOS while parsing a string") + end + end + + return chunk, str +end + + +function Decoder.parse_escape(self, chunk) + local str = "" + chunk = self:fetch_atleast(chunk:sub(2), 1) + local char = chunk:sub(1, 1) + chunk = chunk:sub(2) + + if char == '"' then + return chunk, '"' + elseif char == "\\" then + return chunk, "\\" + elseif char == "u" then + chunk = self:fetch_atleast(chunk, 4) + local s1, s2 = chunk:sub(1, 2), chunk:sub(3, 4) + s1, s2 = tonumber(s1, 16), tonumber(s2, 16) + assert(s1 and s2, "Invalid Unicode character") + + -- ToDo: Unicode support + return chunk:sub(5), s1 == 0 and string.char(s2) or "" + elseif char == "/" then + return chunk, "/" + elseif char == "b" then + return chunk, "\b" + elseif char == "f" then + return chunk, "\f" + elseif char == "n" then + return chunk, "\n" + elseif char == "r" then + return chunk, "\r" + elseif char == "t" then + return chunk, "\t" + else + error("Unexpected escaping sequence '\\%s'" % char) + end +end + + +function Decoder.parse_array(self, chunk) + chunk = chunk:sub(2) + local array = {} + local nextp = 1 + + local chunk, object = self:parse_delimiter(chunk, "%]") + + if object then + return chunk, array + end + + repeat + chunk, object = self:dispatch(chunk, nil, true) + table.insert(array, nextp, object) + nextp = nextp + 1 + + chunk, object = self:parse_delimiter(chunk, ",%]") + assert(object, "Delimiter expected") + until object == "]" + + return chunk, array +end + + +function Decoder.parse_object(self, chunk) + chunk = chunk:sub(2) + local array = {} + local name + + local chunk, object = self:parse_delimiter(chunk, "}") + + if object then + return chunk, array + end + + repeat + chunk = self:parse_space(chunk) + assert(chunk, "Unexpected EOS") + + chunk, name = self:parse_string(chunk) + + chunk, object = self:parse_delimiter(chunk, ":") + assert(object, "Separator expected") + + chunk, object = self:dispatch(chunk, nil, true) + array[name] = object + + chunk, object = self:parse_delimiter(chunk, ",}") + assert(object, "Delimiter expected") + until object == "}" + + return chunk, array +end + + +function Decoder.parse_delimiter(self, chunk, delimiter) + while true do + chunk = self:fetch_atleast(chunk, 1) + local char = chunk:sub(1, 1) + if char:match("%s") then + chunk = self:parse_space(chunk) + assert(chunk, "Unexpected EOS") + elseif char:match("[%s]" % delimiter) then + return chunk:sub(2), char + else + return chunk, nil + end + end +end + + +Decoder.parsers = { + ['"'] = Decoder.parse_string, + ['t'] = Decoder.parse_true, + ['f'] = Decoder.parse_false, + ['n'] = Decoder.parse_null, + ['['] = Decoder.parse_array, + ['{'] = Decoder.parse_object +} + + +--- Create a new Active JSON-Decoder. +-- @class function +-- @name ActiveDecoder +-- @param customnull Use luci.json.null instead of nil for decoding null +-- @return Active JSON-Decoder +ActiveDecoder = util.class(Decoder) + +function ActiveDecoder.__init__(self, source, customnull) + Decoder.__init__(self, customnull) + self.source = source + self.chunk = nil + getmetatable(self).__call = self.get +end + + +--- Fetches one JSON-object from given source +-- @return Decoded object +function ActiveDecoder.get(self) + local chunk, src_err, object + if not self.chunk then + chunk, src_err = self.source() + else + chunk = self.chunk + end + + self.chunk, object = self:dispatch(chunk, src_err, true) + return object +end + + +function ActiveDecoder.fetch(self) + local chunk, src_err = self.source() + assert(chunk or not src_err, src_err) + return chunk +end diff --git a/Mi_Lua/luci/ltn12.lua b/Mi_Lua/luci/ltn12.lua new file mode 100644 index 0000000..1e1cc6b --- /dev/null +++ b/Mi_Lua/luci/ltn12.lua @@ -0,0 +1,391 @@ +--[[ +LuaSocket 2.0.2 license +Copyright � 2004-2007 Diego Nehab + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +]]-- +--[[ + Changes made by LuCI project: + * Renamed to luci.ltn12 to avoid collisions with luasocket + * Added inline documentation +]]-- +----------------------------------------------------------------------------- +-- LTN12 - Filters, sources, sinks and pumps. +-- LuaSocket toolkit. +-- Author: Diego Nehab +-- RCS ID: $Id: ltn12.lua 3212 2008-09-09 12:44:41Z Cyrus $ +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Declare module +----------------------------------------------------------------------------- +local string = require("string") +local table = require("table") +local base = _G + +--- Diego Nehab's LTN12 - Filters, sources, sinks and pumps. +-- See http://lua-users.org/wiki/FiltersSourcesAndSinks for design concepts +module("luci.ltn12") + +filter = {} +source = {} +sink = {} +pump = {} + +-- 2048 seems to be better in windows... +BLOCKSIZE = 2048 +_VERSION = "LTN12 1.0.1" + +----------------------------------------------------------------------------- +-- Filter stuff +----------------------------------------------------------------------------- + +--- LTN12 Filter constructors +-- @class module +-- @name luci.ltn12.filter + +--- Return a high level filter that cycles a low-level filter +-- by passing it each chunk and updating a context between calls. +-- @param low Low-level filter +-- @param ctx Context +-- @param extra Extra argument passed to the low-level filter +-- @return LTN12 filter +function filter.cycle(low, ctx, extra) + base.assert(low) + return function(chunk) + local ret + ret, ctx = low(ctx, chunk, extra) + return ret + end +end + +--- Chain a bunch of filters together. +-- (thanks to Wim Couwenberg) +-- @param ... filters to be chained +-- @return LTN12 filter +function filter.chain(...) + local n = table.getn(arg) + local top, index = 1, 1 + local retry = "" + return function(chunk) + retry = chunk and retry + while true do + if index == top then + chunk = arg[index](chunk) + if chunk == "" or top == n then return chunk + elseif chunk then index = index + 1 + else + top = top+1 + index = top + end + else + chunk = arg[index](chunk or "") + if chunk == "" then + index = index - 1 + chunk = retry + elseif chunk then + if index == n then return chunk + else index = index + 1 end + else base.error("filter returned inappropriate nil") end + end + end + end +end + +----------------------------------------------------------------------------- +-- Source stuff +----------------------------------------------------------------------------- + +--- LTN12 Source constructors +-- @class module +-- @name luci.ltn12.source + +-- create an empty source +local function empty() + return nil +end + +--- Create an empty source. +-- @return LTN12 source +function source.empty() + return empty +end + +--- Return a source that just outputs an error. +-- @param err Error object +-- @return LTN12 source +function source.error(err) + return function() + return nil, err + end +end + +--- Create a file source. +-- @param handle File handle ready for reading +-- @param io_err IO error object +-- @return LTN12 source +function source.file(handle, io_err) + if handle then + return function() + local chunk = handle:read(BLOCKSIZE) + if not chunk then handle:close() end + return chunk + end + else return source.error(io_err or "unable to open file") end +end + +--- Turn a fancy source into a simple source. +-- @param src fancy source +-- @return LTN12 source +function source.simplify(src) + base.assert(src) + return function() + local chunk, err_or_new = src() + src = err_or_new or src + if not chunk then return nil, err_or_new + else return chunk end + end +end + +--- Create a string source. +-- @param s Data +-- @return LTN12 source +function source.string(s) + if s then + local i = 1 + return function() + local chunk = string.sub(s, i, i+BLOCKSIZE-1) + i = i + BLOCKSIZE + if chunk ~= "" then return chunk + else return nil end + end + else return source.empty() end +end + +--- Creates rewindable source. +-- @param src LTN12 source to be made rewindable +-- @return LTN12 source +function source.rewind(src) + base.assert(src) + local t = {} + return function(chunk) + if not chunk then + chunk = table.remove(t) + if not chunk then return src() + else return chunk end + else + t[#t+1] = chunk + end + end +end + +--- Chain a source and a filter together. +-- @param src LTN12 source +-- @param f LTN12 filter +-- @return LTN12 source +function source.chain(src, f) + base.assert(src and f) + local last_in, last_out = "", "" + local state = "feeding" + local err + return function() + if not last_out then + base.error('source is empty!', 2) + end + while true do + if state == "feeding" then + last_in, err = src() + if err then return nil, err end + last_out = f(last_in) + if not last_out then + if last_in then + base.error('filter returned inappropriate nil') + else + return nil + end + elseif last_out ~= "" then + state = "eating" + if last_in then last_in = "" end + return last_out + end + else + last_out = f(last_in) + if last_out == "" then + if last_in == "" then + state = "feeding" + else + base.error('filter returned ""') + end + elseif not last_out then + if last_in then + base.error('filter returned inappropriate nil') + else + return nil + end + else + return last_out + end + end + end + end +end + +--- Create a source that produces contents of several sources. +-- Sources will be used one after the other, as if they were concatenated +-- (thanks to Wim Couwenberg) +-- @param ... LTN12 sources +-- @return LTN12 source +function source.cat(...) + local src = table.remove(arg, 1) + return function() + while src do + local chunk, err = src() + if chunk then return chunk end + if err then return nil, err end + src = table.remove(arg, 1) + end + end +end + +----------------------------------------------------------------------------- +-- Sink stuff +----------------------------------------------------------------------------- + +--- LTN12 sink constructors +-- @class module +-- @name luci.ltn12.sink + +--- Create a sink that stores into a table. +-- @param t output table to store into +-- @return LTN12 sink +function sink.table(t) + t = t or {} + local f = function(chunk, err) + if chunk then t[#t+1] = chunk end + return 1 + end + return f, t +end + +--- Turn a fancy sink into a simple sink. +-- @param snk fancy sink +-- @return LTN12 sink +function sink.simplify(snk) + base.assert(snk) + return function(chunk, err) + local ret, err_or_new = snk(chunk, err) + if not ret then return nil, err_or_new end + snk = err_or_new or snk + return 1 + end +end + +--- Create a file sink. +-- @param handle file handle to write to +-- @param io_err IO error +-- @return LTN12 sink +function sink.file(handle, io_err) + if handle then + return function(chunk, err) + if not chunk then + handle:close() + return 1 + else return handle:write(chunk) end + end + else return sink.error(io_err or "unable to open file") end +end + +-- creates a sink that discards data +local function null() + return 1 +end + +--- Create a sink that discards data. +-- @return LTN12 sink +function sink.null() + return null +end + +--- Create a sink that just returns an error. +-- @param err Error object +-- @return LTN12 sink +function sink.error(err) + return function() + return nil, err + end +end + +--- Chain a sink with a filter. +-- @param f LTN12 filter +-- @param snk LTN12 sink +-- @return LTN12 sink +function sink.chain(f, snk) + base.assert(f and snk) + return function(chunk, err) + if chunk ~= "" then + local filtered = f(chunk) + local done = chunk and "" + while true do + local ret, snkerr = snk(filtered, err) + if not ret then return nil, snkerr end + if filtered == done then return 1 end + filtered = f(done) + end + else return 1 end + end +end + +----------------------------------------------------------------------------- +-- Pump stuff +----------------------------------------------------------------------------- + +--- LTN12 pump functions +-- @class module +-- @name luci.ltn12.pump + +--- Pump one chunk from the source to the sink. +-- @param src LTN12 source +-- @param snk LTN12 sink +-- @return Chunk of data or nil if an error occured +-- @return Error object +function pump.step(src, snk) + local chunk, src_err = src() + local ret, snk_err = snk(chunk, src_err) + if chunk and ret then return 1 + else return nil, src_err or snk_err end +end + +--- Pump all data from a source to a sink, using a step function. +-- @param src LTN12 source +-- @param snk LTN12 sink +-- @param step step function (optional) +-- @return 1 if the operation succeeded otherwise nil +-- @return Error object +function pump.all(src, snk, step) + base.assert(src and snk) + step = step or pump.step + while true do + local ret, err = step(src, snk) + if not ret then + if err then return nil, err + else return 1 end + end + end +end + diff --git a/Mi_Lua/luci/model/cbi/admin_network/proto_dhcp.lua b/Mi_Lua/luci/model/cbi/admin_network/proto_dhcp.lua new file mode 100644 index 0000000..8d2bcc6 --- /dev/null +++ b/Mi_Lua/luci/model/cbi/admin_network/proto_dhcp.lua @@ -0,0 +1,88 @@ +--[[ +LuCI - Lua Configuration Interface + +Copyright 2011-2012 Jo-Philipp Wich + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 +]]-- + +local map, section, net = ... +local ifc = net:get_interface() + +local hostname, accept_ra, send_rs +local bcast, defaultroute, peerdns, dns, metric, clientid, vendorclass + + +hostname = section:taboption("general", Value, "hostname", + translate("Hostname to send when requesting DHCP")) + +hostname.placeholder = luci.sys.hostname() +hostname.datatype = "hostname" + + +if luci.model.network:has_ipv6() then + + accept_ra = s:taboption("general", Flag, "accept_ra", translate("Accept router advertisements")) + accept_ra.default = accept_ra.enabled + + + send_rs = s:taboption("general", Flag, "send_rs", translate("Send router solicitations")) + send_rs.default = send_rs.disabled + send_rs:depends("accept_ra", "") + +end + +bcast = section:taboption("advanced", Flag, "broadcast", + translate("Use broadcast flag"), + translate("Required for certain ISPs, e.g. Charter with DOCSIS 3")) + +bcast.default = bcast.disabled + + +defaultroute = section:taboption("advanced", Flag, "defaultroute", + translate("Use default gateway"), + translate("If unchecked, no default route is configured")) + +defaultroute.default = defaultroute.enabled + + +peerdns = section:taboption("advanced", Flag, "peerdns", + translate("Use DNS servers advertised by peer"), + translate("If unchecked, the advertised DNS server addresses are ignored")) + +peerdns.default = peerdns.enabled + + +dns = section:taboption("advanced", DynamicList, "dns", + translate("Use custom DNS servers")) + +dns:depends("peerdns", "") +dns.datatype = "ipaddr" +dns.cast = "string" + + +metric = section:taboption("advanced", Value, "metric", + translate("Use gateway metric")) + +metric.placeholder = "0" +metric.datatype = "uinteger" + + +clientid = section:taboption("advanced", Value, "clientid", + translate("Client ID to send when requesting DHCP")) + + +vendorclass = section:taboption("advanced", Value, "vendorid", + translate("Vendor Class to send when requesting DHCP")) + + +luci.tools.proto.opt_macaddr(section, ifc, translate("Override MAC address")) + + +mtu = section:taboption("advanced", Value, "mtu", translate("Override MTU")) +mtu.placeholder = "1500" +mtu.datatype = "max(1500)" diff --git a/Mi_Lua/luci/model/cbi/admin_network/proto_l2tp.lua b/Mi_Lua/luci/model/cbi/admin_network/proto_l2tp.lua new file mode 100644 index 0000000..f5de45d --- /dev/null +++ b/Mi_Lua/luci/model/cbi/admin_network/proto_l2tp.lua @@ -0,0 +1,69 @@ +--[[ +LuCI - Lua Configuration Interface + +Copyright 2011 Jo-Philipp Wich + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 +]]-- + +local map, section, net = ... + +local server, username, password +local ipv6, defaultroute, metric, peerdns, dns, mtu + + +server = section:taboption("general", Value, "server", translate("L2TP Server")) +server.datatype = "host" + + +username = section:taboption("general", Value, "username", translate("PAP/CHAP username")) + + +password = section:taboption("general", Value, "password", translate("PAP/CHAP password")) +password.password = true + +if luci.model.network:has_ipv6() then + + ipv6 = section:taboption("advanced", Flag, "ipv6", + translate("Enable IPv6 negotiation on the PPP link")) + + ipv6.default = ipv6.disabled + +end + +defaultroute = section:taboption("advanced", Flag, "defaultroute", + translate("Use default gateway"), + translate("If unchecked, no default route is configured")) + +defaultroute.default = defaultroute.enabled + + +metric = section:taboption("advanced", Value, "metric", + translate("Use gateway metric")) + +metric.placeholder = "0" +metric.datatype = "uinteger" +metric:depends("defaultroute", defaultroute.enabled) + + +peerdns = section:taboption("advanced", Flag, "peerdns", + translate("Use DNS servers advertised by peer"), + translate("If unchecked, the advertised DNS server addresses are ignored")) + +peerdns.default = peerdns.enabled + + +dns = section:taboption("advanced", DynamicList, "dns", + translate("Use custom DNS servers")) + +dns:depends("peerdns", "") +dns.datatype = "ipaddr" +dns.cast = "string" + +mtu = section:taboption("advanced", Value, "mtu", translate("Override MTU")) +mtu.placeholder = "1500" +mtu.datatype = "max(1500)" diff --git a/Mi_Lua/luci/model/cbi/admin_network/proto_none.lua b/Mi_Lua/luci/model/cbi/admin_network/proto_none.lua new file mode 100644 index 0000000..0e34b67 --- /dev/null +++ b/Mi_Lua/luci/model/cbi/admin_network/proto_none.lua @@ -0,0 +1,13 @@ +--[[ +LuCI - Lua Configuration Interface + +Copyright 2011 Jo-Philipp Wich + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 +]]-- + +local map, section, net = ... diff --git a/Mi_Lua/luci/model/cbi/admin_network/proto_ppp.lua b/Mi_Lua/luci/model/cbi/admin_network/proto_ppp.lua new file mode 100644 index 0000000..00760b9 --- /dev/null +++ b/Mi_Lua/luci/model/cbi/admin_network/proto_ppp.lua @@ -0,0 +1,136 @@ +--[[ +LuCI - Lua Configuration Interface + +Copyright 2011 Jo-Philipp Wich + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 +]]-- + +local map, section, net = ... + +local device, username, password +local ipv6, defaultroute, metric, peerdns, dns, + keepalive_failure, keepalive_interval, demand, mtu + + +device = section:taboption("general", Value, "device", translate("Modem device")) +device.rmempty = false + +local device_suggestions = nixio.fs.glob("/dev/tty*S*") + or nixio.fs.glob("/dev/tts/*") + +if device_suggestions then + local node + for node in device_suggestions do + device:value(node) + end +end + + +username = section:taboption("general", Value, "username", translate("PAP/CHAP username")) + + +password = section:taboption("general", Value, "password", translate("PAP/CHAP password")) +password.password = true + + +if luci.model.network:has_ipv6() then + + ipv6 = section:taboption("advanced", Flag, "ipv6", + translate("Enable IPv6 negotiation on the PPP link")) + + ipv6.default = ipv6.disabled + +end + + +defaultroute = section:taboption("advanced", Flag, "defaultroute", + translate("Use default gateway"), + translate("If unchecked, no default route is configured")) + +defaultroute.default = defaultroute.enabled + + +metric = section:taboption("advanced", Value, "metric", + translate("Use gateway metric")) + +metric.placeholder = "0" +metric.datatype = "uinteger" +metric:depends("defaultroute", defaultroute.enabled) + + +peerdns = section:taboption("advanced", Flag, "peerdns", + translate("Use DNS servers advertised by peer"), + translate("If unchecked, the advertised DNS server addresses are ignored")) + +peerdns.default = peerdns.enabled + + +dns = section:taboption("advanced", DynamicList, "dns", + translate("Use custom DNS servers")) + +dns:depends("peerdns", "") +dns.datatype = "ipaddr" +dns.cast = "string" + + +keepalive_failure = section:taboption("advanced", Value, "_keepalive_failure", + translate("LCP echo failure threshold"), + translate("Presume peer to be dead after given amount of LCP echo failures, use 0 to ignore failures")) + +function keepalive_failure.cfgvalue(self, section) + local v = m:get(section, "keepalive") + if v and #v > 0 then + return tonumber(v:match("^(%d+)[ ,]+%d+") or v) + end +end + +function keepalive_failure.write() end +function keepalive_failure.remove() end + +keepalive_failure.placeholder = "0" +keepalive_failure.datatype = "uinteger" + + +keepalive_interval = section:taboption("advanced", Value, "_keepalive_interval", + translate("LCP echo interval"), + translate("Send LCP echo requests at the given interval in seconds, only effective in conjunction with failure threshold")) + +function keepalive_interval.cfgvalue(self, section) + local v = m:get(section, "keepalive") + if v and #v > 0 then + return tonumber(v:match("^%d+[ ,]+(%d+)")) + end +end + +function keepalive_interval.write(self, section, value) + local f = tonumber(keepalive_failure:formvalue(section)) or 0 + local i = tonumber(value) or 5 + if i < 1 then i = 1 end + if f > 0 then + m:set(section, "keepalive", "%d %d" %{ f, i }) + else + m:del(section, "keepalive") + end +end + +keepalive_interval.remove = keepalive_interval.write +keepalive_interval.placeholder = "5" +keepalive_interval.datatype = "min(1)" + + +demand = section:taboption("advanced", Value, "demand", + translate("Inactivity timeout"), + translate("Close inactive connection after the given amount of seconds, use 0 to persist connection")) + +demand.placeholder = "0" +demand.datatype = "uinteger" + + +mtu = section:taboption("advanced", Value, "mtu", translate("Override MTU")) +mtu.placeholder = "1500" +mtu.datatype = "max(1500)" diff --git a/Mi_Lua/luci/model/cbi/admin_network/proto_pppoa.lua b/Mi_Lua/luci/model/cbi/admin_network/proto_pppoa.lua new file mode 100644 index 0000000..5d4c84d --- /dev/null +++ b/Mi_Lua/luci/model/cbi/admin_network/proto_pppoa.lua @@ -0,0 +1,142 @@ +--[[ +LuCI - Lua Configuration Interface + +Copyright 2011 Jo-Philipp Wich + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 +]]-- + +local map, section, net = ... + +local encaps, atmdev, vci, vpi, username, password +local ipv6, defaultroute, metric, peerdns, dns, + keepalive_failure, keepalive_interval, demand, mtu + + +encaps = section:taboption("general", ListValue, "encaps", translate("PPPoA Encapsulation")) +encaps:value("vc", "VC-Mux") +encaps:value("llc", "LLC") + + +atmdev = section:taboption("general", Value, "atmdev", translate("ATM device number")) +atmdev.default = "0" +atmdev.datatype = "uinteger" + + +vci = section:taboption("general", Value, "vci", translate("ATM Virtual Channel Identifier (VCI)")) +vci.default = "35" +vci.datatype = "uinteger" + + +vpi = section:taboption("general", Value, "vpi", translate("ATM Virtual Path Identifier (VPI)")) +vpi.default = "8" +vpi.datatype = "uinteger" + + +username = section:taboption("general", Value, "username", translate("PAP/CHAP username")) + + +password = section:taboption("general", Value, "password", translate("PAP/CHAP password")) +password.password = true + + +if luci.model.network:has_ipv6() then + + ipv6 = section:taboption("advanced", Flag, "ipv6", + translate("Enable IPv6 negotiation on the PPP link")) + + ipv6.default = ipv6.disabled + +end + + +defaultroute = section:taboption("advanced", Flag, "defaultroute", + translate("Use default gateway"), + translate("If unchecked, no default route is configured")) + +defaultroute.default = defaultroute.enabled + + +metric = section:taboption("advanced", Value, "metric", + translate("Use gateway metric")) + +metric.placeholder = "0" +metric.datatype = "uinteger" +metric:depends("defaultroute", defaultroute.enabled) + + +peerdns = section:taboption("advanced", Flag, "peerdns", + translate("Use DNS servers advertised by peer"), + translate("If unchecked, the advertised DNS server addresses are ignored")) + +peerdns.default = peerdns.enabled + + +dns = section:taboption("advanced", DynamicList, "dns", + translate("Use custom DNS servers")) + +dns:depends("peerdns", "") +dns.datatype = "ipaddr" +dns.cast = "string" + + +keepalive_failure = section:taboption("advanced", Value, "_keepalive_failure", + translate("LCP echo failure threshold"), + translate("Presume peer to be dead after given amount of LCP echo failures, use 0 to ignore failures")) + +function keepalive_failure.cfgvalue(self, section) + local v = m:get(section, "keepalive") + if v and #v > 0 then + return tonumber(v:match("^(%d+)[ ,]+%d+") or v) + end +end + +function keepalive_failure.write() end +function keepalive_failure.remove() end + +keepalive_failure.placeholder = "0" +keepalive_failure.datatype = "uinteger" + + +keepalive_interval = section:taboption("advanced", Value, "_keepalive_interval", + translate("LCP echo interval"), + translate("Send LCP echo requests at the given interval in seconds, only effective in conjunction with failure threshold")) + +function keepalive_interval.cfgvalue(self, section) + local v = m:get(section, "keepalive") + if v and #v > 0 then + return tonumber(v:match("^%d+[ ,]+(%d+)")) + end +end + +function keepalive_interval.write(self, section, value) + local f = tonumber(keepalive_failure:formvalue(section)) or 0 + local i = tonumber(value) or 5 + if i < 1 then i = 1 end + if f > 0 then + m:set(section, "keepalive", "%d %d" %{ f, i }) + else + m:del(section, "keepalive") + end +end + +keepalive_interval.remove = keepalive_interval.write +keepalive_interval.placeholder = "5" +keepalive_interval.datatype = "min(1)" + + +demand = section:taboption("advanced", Value, "demand", + translate("Inactivity timeout"), + translate("Close inactive connection after the given amount of seconds, use 0 to persist connection")) + +demand.placeholder = "0" +demand.datatype = "uinteger" + + +mtu = section:taboption("advanced", Value, "mtu", translate("Override MTU")) +mtu.placeholder = "1500" +mtu.datatype = "max(1500)" diff --git a/Mi_Lua/luci/model/cbi/admin_network/proto_pppoe.lua b/Mi_Lua/luci/model/cbi/admin_network/proto_pppoe.lua new file mode 100644 index 0000000..4f19ac3 --- /dev/null +++ b/Mi_Lua/luci/model/cbi/admin_network/proto_pppoe.lua @@ -0,0 +1,136 @@ +--[[ +LuCI - Lua Configuration Interface + +Copyright 2011 Jo-Philipp Wich + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 +]]-- + +local map, section, net = ... + +local username, password, ac, service +local ipv6, defaultroute, metric, peerdns, dns, + keepalive_failure, keepalive_interval, demand, mtu + + +username = section:taboption("general", Value, "username", translate("PAP/CHAP username")) + + +password = section:taboption("general", Value, "password", translate("PAP/CHAP password")) +password.password = true + + +ac = section:taboption("general", Value, "ac", + translate("Access Concentrator"), + translate("Leave empty to autodetect")) + +ac.placeholder = translate("auto") + + +service = section:taboption("general", Value, "service", + translate("Service Name"), + translate("Leave empty to autodetect")) + +service.placeholder = translate("auto") + + +if luci.model.network:has_ipv6() then + + ipv6 = section:taboption("advanced", Flag, "ipv6", + translate("Enable IPv6 negotiation on the PPP link")) + + ipv6.default = ipv6.disabled + +end + + +defaultroute = section:taboption("advanced", Flag, "defaultroute", + translate("Use default gateway"), + translate("If unchecked, no default route is configured")) + +defaultroute.default = defaultroute.enabled + + +metric = section:taboption("advanced", Value, "metric", + translate("Use gateway metric")) + +metric.placeholder = "0" +metric.datatype = "uinteger" +metric:depends("defaultroute", defaultroute.enabled) + + +peerdns = section:taboption("advanced", Flag, "peerdns", + translate("Use DNS servers advertised by peer"), + translate("If unchecked, the advertised DNS server addresses are ignored")) + +peerdns.default = peerdns.enabled + + +dns = section:taboption("advanced", DynamicList, "dns", + translate("Use custom DNS servers")) + +dns:depends("peerdns", "") +dns.datatype = "ipaddr" +dns.cast = "string" + + +keepalive_failure = section:taboption("advanced", Value, "_keepalive_failure", + translate("LCP echo failure threshold"), + translate("Presume peer to be dead after given amount of LCP echo failures, use 0 to ignore failures")) + +function keepalive_failure.cfgvalue(self, section) + local v = m:get(section, "keepalive") + if v and #v > 0 then + return tonumber(v:match("^(%d+)[ ,]+%d+") or v) + end +end + +function keepalive_failure.write() end +function keepalive_failure.remove() end + +keepalive_failure.placeholder = "0" +keepalive_failure.datatype = "uinteger" + + +keepalive_interval = section:taboption("advanced", Value, "_keepalive_interval", + translate("LCP echo interval"), + translate("Send LCP echo requests at the given interval in seconds, only effective in conjunction with failure threshold")) + +function keepalive_interval.cfgvalue(self, section) + local v = m:get(section, "keepalive") + if v and #v > 0 then + return tonumber(v:match("^%d+[ ,]+(%d+)")) + end +end + +function keepalive_interval.write(self, section, value) + local f = tonumber(keepalive_failure:formvalue(section)) or 0 + local i = tonumber(value) or 5 + if i < 1 then i = 1 end + if f > 0 then + m:set(section, "keepalive", "%d %d" %{ f, i }) + else + m:del(section, "keepalive") + end +end + +keepalive_interval.remove = keepalive_interval.write +keepalive_interval.placeholder = "5" +keepalive_interval.datatype = "min(1)" + + +demand = section:taboption("advanced", Value, "demand", + translate("Inactivity timeout"), + translate("Close inactive connection after the given amount of seconds, use 0 to persist connection")) + +demand.placeholder = "0" +demand.datatype = "uinteger" + + +mtu = section:taboption("advanced", Value, "mtu", translate("Override MTU")) +mtu.placeholder = "1500" +mtu.datatype = "max(1500)" diff --git a/Mi_Lua/luci/model/cbi/admin_network/proto_pptp.lua b/Mi_Lua/luci/model/cbi/admin_network/proto_pptp.lua new file mode 100644 index 0000000..0a2ee8e --- /dev/null +++ b/Mi_Lua/luci/model/cbi/admin_network/proto_pptp.lua @@ -0,0 +1,116 @@ +--[[ +LuCI - Lua Configuration Interface + +Copyright 2011-2012 Jo-Philipp Wich + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 +]]-- + +local map, section, net = ... + +local server, username, password +local defaultroute, metric, peerdns, dns, + keepalive_failure, keepalive_interval, demand, mtu + + +server = section:taboption("general", Value, "server", translate("VPN Server")) +server.datatype = "host" + + +username = section:taboption("general", Value, "username", translate("PAP/CHAP username")) + + +password = section:taboption("general", Value, "password", translate("PAP/CHAP password")) +password.password = true + + +defaultroute = section:taboption("advanced", Flag, "defaultroute", + translate("Use default gateway"), + translate("If unchecked, no default route is configured")) + +defaultroute.default = defaultroute.enabled + + +metric = section:taboption("advanced", Value, "metric", + translate("Use gateway metric")) + +metric.placeholder = "0" +metric.datatype = "uinteger" +metric:depends("defaultroute", defaultroute.enabled) + + +peerdns = section:taboption("advanced", Flag, "peerdns", + translate("Use DNS servers advertised by peer"), + translate("If unchecked, the advertised DNS server addresses are ignored")) + +peerdns.default = peerdns.enabled + + +dns = section:taboption("advanced", DynamicList, "dns", + translate("Use custom DNS servers")) + +dns:depends("peerdns", "") +dns.datatype = "ipaddr" +dns.cast = "string" + + +keepalive_failure = section:taboption("advanced", Value, "_keepalive_failure", + translate("LCP echo failure threshold"), + translate("Presume peer to be dead after given amount of LCP echo failures, use 0 to ignore failures")) + +function keepalive_failure.cfgvalue(self, section) + local v = m:get(section, "keepalive") + if v and #v > 0 then + return tonumber(v:match("^(%d+)[ ,]+%d+") or v) + end +end + +function keepalive_failure.write() end +function keepalive_failure.remove() end + +keepalive_failure.placeholder = "0" +keepalive_failure.datatype = "uinteger" + + +keepalive_interval = section:taboption("advanced", Value, "_keepalive_interval", + translate("LCP echo interval"), + translate("Send LCP echo requests at the given interval in seconds, only effective in conjunction with failure threshold")) + +function keepalive_interval.cfgvalue(self, section) + local v = m:get(section, "keepalive") + if v and #v > 0 then + return tonumber(v:match("^%d+[ ,]+(%d+)")) + end +end + +function keepalive_interval.write(self, section, value) + local f = tonumber(keepalive_failure:formvalue(section)) or 0 + local i = tonumber(value) or 5 + if i < 1 then i = 1 end + if f > 0 then + m:set(section, "keepalive", "%d %d" %{ f, i }) + else + m:del(section, "keepalive") + end +end + +keepalive_interval.remove = keepalive_interval.write +keepalive_interval.placeholder = "5" +keepalive_interval.datatype = "min(1)" + + +demand = section:taboption("advanced", Value, "demand", + translate("Inactivity timeout"), + translate("Close inactive connection after the given amount of seconds, use 0 to persist connection")) + +demand.placeholder = "0" +demand.datatype = "uinteger" + + +mtu = section:taboption("advanced", Value, "mtu", translate("Override MTU")) +mtu.placeholder = "1500" +mtu.datatype = "max(1500)" diff --git a/Mi_Lua/luci/model/cbi/admin_network/proto_static.lua b/Mi_Lua/luci/model/cbi/admin_network/proto_static.lua new file mode 100644 index 0000000..e6bfcbb --- /dev/null +++ b/Mi_Lua/luci/model/cbi/admin_network/proto_static.lua @@ -0,0 +1,83 @@ +--[[ +LuCI - Lua Configuration Interface + +Copyright 2011 Jo-Philipp Wich + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 +]]-- + +local map, section, net = ... +local ifc = net:get_interface() + +local ipaddr, netmask, gateway, broadcast, dns, accept_ra, send_rs, ip6addr, ip6gw +local mtu, metric + + +ipaddr = section:taboption("general", Value, "ipaddr", translate("IPv4 address")) +ipaddr.datatype = "ip4addr" + + +netmask = section:taboption("general", Value, "netmask", + translate("IPv4 netmask")) + +netmask.datatype = "ip4addr" +netmask:value("255.255.255.0") +netmask:value("255.255.0.0") +netmask:value("255.0.0.0") + + +gateway = section:taboption("general", Value, "gateway", translate("IPv4 gateway")) +gateway.datatype = "ip4addr" + + +broadcast = section:taboption("general", Value, "broadcast", translate("IPv4 broadcast")) +broadcast.datatype = "ip4addr" + + +dns = section:taboption("general", DynamicList, "dns", + translate("Use custom DNS servers")) + +dns.datatype = "ipaddr" +dns.cast = "string" + + +if luci.model.network:has_ipv6() then + + accept_ra = s:taboption("general", Flag, "accept_ra", translate("Accept router advertisements")) + accept_ra.default = accept_ra.disabled + + + send_rs = s:taboption("general", Flag, "send_rs", translate("Send router solicitations")) + send_rs.default = send_rs.enabled + send_rs:depends("accept_ra", "") + + + ip6addr = section:taboption("general", Value, "ip6addr", translate("IPv6 address")) + ip6addr.datatype = "ip6addr" + ip6addr:depends("accept_ra", "") + + + ip6gw = section:taboption("general", Value, "ip6gw", translate("IPv6 gateway")) + ip6gw.datatype = "ip6addr" + ip6gw:depends("accept_ra", "") + +end + + +luci.tools.proto.opt_macaddr(section, ifc, translate("Override MAC address")) + + +mtu = section:taboption("advanced", Value, "mtu", translate("Override MTU")) +mtu.placeholder = "1500" +mtu.datatype = "max(1500)" + + +metric = section:taboption("advanced", Value, "metric", + translate("Use gateway metric")) + +metric.placeholder = "0" +metric.datatype = "uinteger" diff --git a/Mi_Lua/luci/model/cbi/firewall/custom.lua b/Mi_Lua/luci/model/cbi/firewall/custom.lua new file mode 100644 index 0000000..ceb04ec --- /dev/null +++ b/Mi_Lua/luci/model/cbi/firewall/custom.lua @@ -0,0 +1,38 @@ +--[[ +LuCI - Lua Configuration Interface + +Copyright 2011 Jo-Philipp Wich + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +$Id: custom.lua 8108 2011-12-19 21:16:31Z jow $ +]]-- + +local fs = require "nixio.fs" + +local f = SimpleForm("firewall", + translate("Firewall - Custom Rules"), + translate("Custom rules allow you to execute arbritary iptables commands \ + which are not otherwise covered by the firewall framework. \ + The commands are executed after each firewall restart, right after \ + the default ruleset has been loaded.")) + +local o = f:field(Value, "_custom") + +o.template = "cbi/tvalue" +o.rows = 20 + +function o.cfgvalue(self, section) + return fs.readfile("/etc/firewall.user") +end + +function o.write(self, section, value) + value = value:gsub("\r\n?", "\n") + fs.writefile("/etc/firewall.user", value) +end + +return f diff --git a/Mi_Lua/luci/model/cbi/firewall/forward-details.lua b/Mi_Lua/luci/model/cbi/firewall/forward-details.lua new file mode 100644 index 0000000..4d67e67 --- /dev/null +++ b/Mi_Lua/luci/model/cbi/firewall/forward-details.lua @@ -0,0 +1,178 @@ +--[[ +LuCI - Lua Configuration Interface + +Copyright 2011 Jo-Philipp Wich + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +$Id: forward-details.lua 8962 2012-08-09 10:03:32Z jow $ +]]-- + +local sys = require "luci.sys" +local dsp = require "luci.dispatcher" +local ft = require "luci.tools.firewall" + +local m, s, o + +arg[1] = arg[1] or "" + +m = Map("firewall", + translate("Firewall - Port Forwards"), + translate("This page allows you to change advanced properties of the port \ + forwarding entry. In most cases there is no need to modify \ + those settings.")) + +m.redirect = dsp.build_url("admin/network/firewall/forwards") + +if m.uci:get("firewall", arg[1]) ~= "redirect" then + luci.http.redirect(m.redirect) + return +else + local name = m:get(arg[1], "name") or m:get(arg[1], "_name") + if not name or #name == 0 then + name = translate("(Unnamed Entry)") + end + m.title = "%s - %s" %{ translate("Firewall - Port Forwards"), name } +end + +local wan_zone = nil + +m.uci:foreach("firewall", "zone", + function(s) + local n = s.network or s.name + if n then + local i + for i in n:gmatch("%S+") do + if i == "wan" then + wan_zone = s.name + return false + end + end + end + end) + +s = m:section(NamedSection, arg[1], "redirect", "") +s.anonymous = true +s.addremove = false + +ft.opt_enabled(s, Button) +ft.opt_name(s, Value, translate("Name")) + + +o = s:option(Value, "proto", translate("Protocol")) +o:value("tcp udp", "TCP+UDP") +o:value("tcp", "TCP") +o:value("udp", "UDP") +o:value("icmp", "ICMP") + +function o.cfgvalue(...) + local v = Value.cfgvalue(...) + if not v or v == "tcpudp" then + return "tcp udp" + end + return v +end + + +o = s:option(Value, "src", translate("Source zone")) +o.nocreate = true +o.default = "wan" +o.template = "cbi/firewall_zonelist" + + +o = s:option(DynamicList, "src_mac", + translate("Source MAC address"), + translate("Only match incoming traffic from these MACs.")) +o.rmempty = true +o.datatype = "neg(macaddr)" +o.placeholder = translate("any") + +luci.sys.net.mac_hints(function(mac, name) + o:value(mac, "%s (%s)" %{ mac, name }) +end) + + +o = s:option(Value, "src_ip", + translate("Source IP address"), + translate("Only match incoming traffic from this IP or range.")) +o.rmempty = true +o.datatype = "neg(ip4addr)" +o.placeholder = translate("any") + +luci.sys.net.ipv4_hints(function(ip, name) + o:value(ip, "%s (%s)" %{ ip, name }) +end) + + +o = s:option(Value, "src_port", + translate("Source port"), + translate("Only match incoming traffic originating from the given source port or port range on the client host")) +o.rmempty = true +o.datatype = "neg(portrange)" +o.placeholder = translate("any") + + +o = s:option(Value, "src_dip", + translate("External IP address"), + translate("Only match incoming traffic directed at the given IP address.")) + +luci.sys.net.ipv4_hints(function(ip, name) + o:value(ip, "%s (%s)" %{ ip, name }) +end) + + +o.rmempty = true +o.datatype = "neg(ip4addr)" +o.placeholder = translate("any") + + +o = s:option(Value, "src_dport", translate("External port"), + translate("Match incoming traffic directed at the given " .. + "destination port or port range on this host")) +o.datatype = "neg(portrange)" + + + +o = s:option(Value, "dest", translate("Internal zone")) +o.nocreate = true +o.default = "lan" +o.template = "cbi/firewall_zonelist" + + +o = s:option(Value, "dest_ip", translate("Internal IP address"), + translate("Redirect matched incoming traffic to the specified \ + internal host")) +o.datatype = "ip4addr" + +luci.sys.net.ipv4_hints(function(ip, name) + o:value(ip, "%s (%s)" %{ ip, name }) +end) + + +o = s:option(Value, "dest_port", + translate("Internal port"), + translate("Redirect matched incoming traffic to the given port on \ + the internal host")) +o.placeholder = translate("any") +o.datatype = "portrange" + + +o = s:option(Flag, "reflection", translate("Enable NAT Loopback")) +o.rmempty = true +o.default = o.enabled +o:depends("src", wan_zone) +o.cfgvalue = function(...) + return Flag.cfgvalue(...) or "1" +end + + +s:option(Value, "extra", + translate("Extra arguments"), + translate("Passes additional arguments to iptables. Use with care!")) + + +return m diff --git a/Mi_Lua/luci/model/cbi/firewall/forwards.lua b/Mi_Lua/luci/model/cbi/firewall/forwards.lua new file mode 100644 index 0000000..5f7a69b --- /dev/null +++ b/Mi_Lua/luci/model/cbi/firewall/forwards.lua @@ -0,0 +1,144 @@ +--[[ +LuCI - Lua Configuration Interface + +Copyright 2008 Steven Barth +Copyright 2010-2012 Jo-Philipp Wich + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +]]-- + +local ds = require "luci.dispatcher" +local ft = require "luci.tools.firewall" + +m = Map("firewall", translate("Firewall - Port Forwards"), + translate("Port forwarding allows remote computers on the Internet to \ + connect to a specific computer or service within the \ + private LAN.")) + +-- +-- Port Forwards +-- + +s = m:section(TypedSection, "redirect", translate("Port Forwards")) +s.template = "cbi/tblsection" +s.addremove = true +s.anonymous = true +s.sortable = true +s.extedit = ds.build_url("admin/network/firewall/forwards/%s") +s.template_addremove = "firewall/cbi_addforward" + +function s.create(self, section) + local n = m:formvalue("_newfwd.name") + local p = m:formvalue("_newfwd.proto") + local E = m:formvalue("_newfwd.extzone") + local e = m:formvalue("_newfwd.extport") + local I = m:formvalue("_newfwd.intzone") + local a = m:formvalue("_newfwd.intaddr") + local i = m:formvalue("_newfwd.intport") + + if p == "other" or (p and a) then + created = TypedSection.create(self, section) + + self.map:set(created, "target", "DNAT") + self.map:set(created, "src", E or "wan") + self.map:set(created, "dest", I or "lan") + self.map:set(created, "proto", (p ~= "other") and p or "all") + self.map:set(created, "src_dport", e) + self.map:set(created, "dest_ip", a) + self.map:set(created, "dest_port", i) + self.map:set(created, "name", n) + end + + if p ~= "other" then + created = nil + end +end + +function s.parse(self, ...) + TypedSection.parse(self, ...) + if created then + m.uci:save("firewall") + luci.http.redirect(ds.build_url( + "admin/network/firewall/redirect", created + )) + end +end + +function s.filter(self, sid) + return (self.map:get(sid, "target") ~= "SNAT") +end + + +ft.opt_name(s, DummyValue, translate("Name")) + + +local function forward_proto_txt(self, s) + return "%s-%s" %{ + translate("IPv4"), + ft.fmt_proto(self.map:get(s, "proto"), + self.map:get(s, "icmp_type")) or "TCP+UDP" + } +end + +local function forward_src_txt(self, s) + local z = ft.fmt_zone(self.map:get(s, "src"), translate("any zone")) + local a = ft.fmt_ip(self.map:get(s, "src_ip"), translate("any host")) + local p = ft.fmt_port(self.map:get(s, "src_port")) + local m = ft.fmt_mac(self.map:get(s, "src_mac")) + + if p and m then + return translatef("From %s in %s with source %s and %s", a, z, p, m) + elseif p or m then + return translatef("From %s in %s with source %s", a, z, p or m) + else + return translatef("From %s in %s", a, z) + end +end + +local function forward_via_txt(self, s) + local a = ft.fmt_ip(self.map:get(s, "src_dip"), translate("any router IP")) + local p = ft.fmt_port(self.map:get(s, "src_dport")) + + if p then + return translatef("Via %s at %s", a, p) + else + return translatef("Via %s", a) + end +end + +match = s:option(DummyValue, "match", translate("Match")) +match.rawhtml = true +match.width = "50%" +function match.cfgvalue(self, s) + return "%s
%s
%s
" % { + forward_proto_txt(self, s), + forward_src_txt(self, s), + forward_via_txt(self, s) + } +end + + +dest = s:option(DummyValue, "dest", translate("Forward to")) +dest.rawhtml = true +dest.width = "40%" +function dest.cfgvalue(self, s) + local z = ft.fmt_zone(self.map:get(s, "dest"), translate("any zone")) + local a = ft.fmt_ip(self.map:get(s, "dest_ip"), translate("any host")) + local p = ft.fmt_port(self.map:get(s, "dest_port")) or + ft.fmt_port(self.map:get(s, "src_dport")) + + if p then + return translatef("%s, %s in %s", a, p, z) + else + return translatef("%s in %s", a, z) + end +end + +ft.opt_enabled(s, Flag, translate("Enable")).width = "1%" + +return m diff --git a/Mi_Lua/luci/model/cbi/firewall/rule-details.lua b/Mi_Lua/luci/model/cbi/firewall/rule-details.lua new file mode 100644 index 0000000..0086148 --- /dev/null +++ b/Mi_Lua/luci/model/cbi/firewall/rule-details.lua @@ -0,0 +1,338 @@ +--[[ +LuCI - Lua Configuration Interface + +Copyright 2008 Steven Barth +Copyright 2010-2012 Jo-Philipp Wich + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +]]-- + +local sys = require "luci.sys" +local dsp = require "luci.dispatcher" +local nxo = require "nixio" + +local ft = require "luci.tools.firewall" +local nw = require "luci.model.network" +local m, s, o, k, v + +arg[1] = arg[1] or "" + +m = Map("firewall", + translate("Firewall - Traffic Rules"), + translate("This page allows you to change advanced properties of the \ + traffic rule entry, such as matched source and destination \ + hosts.")) + +m.redirect = dsp.build_url("admin/network/firewall/rules") + +nw.init(m.uci) + +local rule_type = m.uci:get("firewall", arg[1]) +if rule_type == "redirect" and m:get(arg[1], "target") ~= "SNAT" then + rule_type = nil +end + +if not rule_type then + luci.http.redirect(m.redirect) + return + +-- +-- SNAT +-- +elseif rule_type == "redirect" then + + local name = m:get(arg[1], "name") or m:get(arg[1], "_name") + if not name or #name == 0 then + name = translate("(Unnamed SNAT)") + else + name = "SNAT %s" % name + end + + m.title = "%s - %s" %{ translate("Firewall - Traffic Rules"), name } + + local wan_zone = nil + + m.uci:foreach("firewall", "zone", + function(s) + local n = s.network or s.name + if n then + local i + for i in n:gmatch("%S+") do + if i == "wan" then + wan_zone = s.name + return false + end + end + end + end) + + s = m:section(NamedSection, arg[1], "redirect", "") + s.anonymous = true + s.addremove = false + + + ft.opt_enabled(s, Button) + ft.opt_name(s, Value, translate("Name")) + + + o = s:option(Value, "proto", + translate("Protocol"), + translate("You may specify multiple by selecting \"-- custom --\" and \ + then entering protocols separated by space.")) + + o:value("all", "All protocols") + o:value("tcp udp", "TCP+UDP") + o:value("tcp", "TCP") + o:value("udp", "UDP") + o:value("icmp", "ICMP") + + function o.cfgvalue(...) + local v = Value.cfgvalue(...) + if not v or v == "tcpudp" then + return "tcp udp" + end + return v + end + + + o = s:option(Value, "src", translate("Source zone")) + o.nocreate = true + o.default = "wan" + o.template = "cbi/firewall_zonelist" + + + o = s:option(DynamicList, "src_mac", translate("Source MAC address")) + o.rmempty = true + o.datatype = "neg(macaddr)" + o.placeholder = translate("any") + + luci.sys.net.mac_hints(function(mac, name) + o:value(mac, "%s (%s)" %{ mac, name }) + end) + + + o = s:option(Value, "src_ip", translate("Source IP address")) + o.rmempty = true + o.datatype = "neg(ipaddr)" + o.placeholder = translate("any") + + luci.sys.net.ipv4_hints(function(ip, name) + o:value(ip, "%s (%s)" %{ ip, name }) + end) + + + o = s:option(Value, "src_port", + translate("Source port"), + translate("Match incoming traffic originating from the given source \ + port or port range on the client host.")) + o.rmempty = true + o.datatype = "neg(portrange)" + o.placeholder = translate("any") + + + o = s:option(Value, "dest", translate("Destination zone")) + o.nocreate = true + o.default = "lan" + o.template = "cbi/firewall_zonelist" + + + o = s:option(Value, "dest_ip", translate("Destination IP address")) + o.datatype = "neg(ip4addr)" + + luci.sys.net.ipv4_hints(function(ip, name) + o:value(ip, "%s (%s)" %{ ip, name }) + end) + + + o = s:option(Value, "dest_port", + translate("Destination port"), + translate("Match forwarded traffic to the given destination port or \ + port range.")) + + o.rmempty = true + o.placeholder = translate("any") + o.datatype = "neg(portrange)" + + + o = s:option(Value, "src_dip", + translate("SNAT IP address"), + translate("Rewrite matched traffic to the given address.")) + o.rmempty = false + o.datatype = "ip4addr" + + for k, v in ipairs(nw:get_interfaces()) do + local a + for k, a in ipairs(v:ipaddrs()) do + o:value(a:host():string(), '%s (%s)' %{ + a:host():string(), v:shortname() + }) + end + end + + + o = s:option(Value, "src_dport", translate("SNAT port"), + translate("Rewrite matched traffic to the given source port. May be \ + left empty to only rewrite the IP address.")) + o.datatype = "portrange" + o.rmempty = true + o.placeholder = translate('Do not rewrite') + + + s:option(Value, "extra", + translate("Extra arguments"), + translate("Passes additional arguments to iptables. Use with care!")) + + +-- +-- Rule +-- +else + local name = m:get(arg[1], "name") or m:get(arg[1], "_name") + if not name or #name == 0 then + name = translate("(Unnamed Rule)") + end + + m.title = "%s - %s" %{ translate("Firewall - Traffic Rules"), name } + + + s = m:section(NamedSection, arg[1], "rule", "") + s.anonymous = true + s.addremove = false + + ft.opt_enabled(s, Button) + ft.opt_name(s, Value, translate("Name")) + + + o = s:option(ListValue, "family", translate("Restrict to address family")) + o.rmempty = true + o:value("", translate("IPv4 and IPv6")) + o:value("ipv4", translate("IPv4 only")) + o:value("ipv6", translate("IPv6 only")) + + + o = s:option(Value, "proto", translate("Protocol")) + o:value("all", translate("Any")) + o:value("tcp udp", "TCP+UDP") + o:value("tcp", "TCP") + o:value("udp", "UDP") + o:value("icmp", "ICMP") + + function o.cfgvalue(...) + local v = Value.cfgvalue(...) + if not v or v == "tcpudp" then + return "tcp udp" + end + return v + end + + + o = s:option(DynamicList, "icmp_type", translate("Match ICMP type")) + o:value("", "any") + o:value("echo-reply") + o:value("destination-unreachable") + o:value("network-unreachable") + o:value("host-unreachable") + o:value("protocol-unreachable") + o:value("port-unreachable") + o:value("fragmentation-needed") + o:value("source-route-failed") + o:value("network-unknown") + o:value("host-unknown") + o:value("network-prohibited") + o:value("host-prohibited") + o:value("TOS-network-unreachable") + o:value("TOS-host-unreachable") + o:value("communication-prohibited") + o:value("host-precedence-violation") + o:value("precedence-cutoff") + o:value("source-quench") + o:value("redirect") + o:value("network-redirect") + o:value("host-redirect") + o:value("TOS-network-redirect") + o:value("TOS-host-redirect") + o:value("echo-request") + o:value("router-advertisement") + o:value("router-solicitation") + o:value("time-exceeded") + o:value("ttl-zero-during-transit") + o:value("ttl-zero-during-reassembly") + o:value("parameter-problem") + o:value("ip-header-bad") + o:value("required-option-missing") + o:value("timestamp-request") + o:value("timestamp-reply") + o:value("address-mask-request") + o:value("address-mask-reply") + + + o = s:option(Value, "src", translate("Source zone")) + o.nocreate = true + o.allowany = true + o.default = "wan" + o.template = "cbi/firewall_zonelist" + + + o = s:option(Value, "src_mac", translate("Source MAC address")) + o.datatype = "list(macaddr)" + o.placeholder = translate("any") + + luci.sys.net.mac_hints(function(mac, name) + o:value(mac, "%s (%s)" %{ mac, name }) + end) + + + o = s:option(Value, "src_ip", translate("Source address")) + o.datatype = "neg(ipaddr)" + o.placeholder = translate("any") + + luci.sys.net.ipv4_hints(function(ip, name) + o:value(ip, "%s (%s)" %{ ip, name }) + end) + + + o = s:option(Value, "src_port", translate("Source port")) + o.datatype = "list(neg(portrange))" + o.placeholder = translate("any") + + + o = s:option(Value, "dest", translate("Destination zone")) + o.nocreate = true + o.allowany = true + o.allowlocal = true + o.template = "cbi/firewall_zonelist" + + + o = s:option(Value, "dest_ip", translate("Destination address")) + o.datatype = "neg(ipaddr)" + o.placeholder = translate("any") + + luci.sys.net.ipv4_hints(function(ip, name) + o:value(ip, "%s (%s)" %{ ip, name }) + end) + + + o = s:option(Value, "dest_port", translate("Destination port")) + o.datatype = "list(neg(portrange))" + o.placeholder = translate("any") + + + o = s:option(ListValue, "target", translate("Action")) + o.default = "ACCEPT" + o:value("DROP", translate("drop")) + o:value("ACCEPT", translate("accept")) + o:value("REJECT", translate("reject")) + o:value("NOTRACK", translate("don't track")) + + + s:option(Value, "extra", + translate("Extra arguments"), + translate("Passes additional arguments to iptables. Use with care!")) +end + +return m diff --git a/Mi_Lua/luci/model/cbi/firewall/rules.lua b/Mi_Lua/luci/model/cbi/firewall/rules.lua new file mode 100644 index 0000000..0f7462b --- /dev/null +++ b/Mi_Lua/luci/model/cbi/firewall/rules.lua @@ -0,0 +1,269 @@ +--[[ +LuCI - Lua Configuration Interface + +Copyright 2008 Steven Barth +Copyright 2010-2012 Jo-Philipp Wich + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +]]-- + +local ds = require "luci.dispatcher" +local ft = require "luci.tools.firewall" + +m = Map("firewall", + translate("Firewall - Traffic Rules"), + translate("Traffic rules define policies for packets traveling between \ + different zones, for example to reject traffic between certain hosts \ + or to open WAN ports on the router.")) + +-- +-- Rules +-- + +s = m:section(TypedSection, "rule", translate("Traffic Rules")) +s.addremove = true +s.anonymous = true +s.sortable = true +s.template = "cbi/tblsection" +s.extedit = ds.build_url("admin/network/firewall/rules/%s") +s.defaults.target = "ACCEPT" +s.template_addremove = "firewall/cbi_addrule" + + +function s.create(self, section) + created = TypedSection.create(self, section) +end + +function s.parse(self, ...) + TypedSection.parse(self, ...) + + local i_n = m:formvalue("_newopen.name") + local i_p = m:formvalue("_newopen.proto") + local i_e = m:formvalue("_newopen.extport") + local i_x = m:formvalue("_newopen.submit") + + local f_n = m:formvalue("_newfwd.name") + local f_s = m:formvalue("_newfwd.src") + local f_d = m:formvalue("_newfwd.dest") + local f_x = m:formvalue("_newfwd.submit") + + if i_x then + created = TypedSection.create(self, section) + + self.map:set(created, "target", "ACCEPT") + self.map:set(created, "src", "wan") + self.map:set(created, "proto", (i_p ~= "other") and i_p or "all") + self.map:set(created, "dest_port", i_e) + self.map:set(created, "name", i_n) + + if i_p ~= "other" and i_e and #i_e > 0 then + created = nil + end + + elseif f_x then + created = TypedSection.create(self, section) + + self.map:set(created, "target", "ACCEPT") + self.map:set(created, "src", f_s) + self.map:set(created, "dest", f_d) + self.map:set(created, "name", f_n) + end + + if created then + m.uci:save("firewall") + luci.http.redirect(ds.build_url( + "admin/network/firewall/rules", created + )) + end +end + +ft.opt_name(s, DummyValue, translate("Name")) + +local function rule_proto_txt(self, s) + local f = self.map:get(s, "family") + local p = ft.fmt_proto(self.map:get(s, "proto"), + self.map:get(s, "icmp_type")) or "TCP+UDP" + + if f and f:match("4") then + return "%s-%s" %{ translate("IPv4"), p } + elseif f and f:match("6") then + return "%s-%s" %{ translate("IPv6"), p } + else + return "%s %s" %{ translate("Any"), p } + end +end + +local function rule_src_txt(self, s) + local z = ft.fmt_zone(self.map:get(s, "src"), translate("any zone")) + local a = ft.fmt_ip(self.map:get(s, "src_ip"), translate("any host")) + local p = ft.fmt_port(self.map:get(s, "src_port")) + local m = ft.fmt_mac(self.map:get(s, "src_mac")) + + if p and m then + return translatef("From %s in %s with source %s and %s", a, z, p, m) + elseif p or m then + return translatef("From %s in %s with source %s", a, z, p or m) + else + return translatef("From %s in %s", a, z) + end +end + +local function rule_dest_txt(self, s) + local z = ft.fmt_zone(self.map:get(s, "dest")) + local p = ft.fmt_port(self.map:get(s, "dest_port")) + + -- Forward + if z then + local a = ft.fmt_ip(self.map:get(s, "dest_ip"), translate("any host")) + if p then + return translatef("To %s, %s in %s", a, p, z) + else + return translatef("To %s in %s", a, z) + end + + -- Input + else + local a = ft.fmt_ip(self.map:get(s, "dest_ip"), + translate("any router IP")) + + if p then + return translatef("To %s at %s on this device", a, p) + else + return translatef("To %s on this device", a) + end + end +end + +local function snat_dest_txt(self, s) + local z = ft.fmt_zone(self.map:get(s, "dest"), translate("any zone")) + local a = ft.fmt_ip(self.map:get(s, "dest_ip"), translate("any host")) + local p = ft.fmt_port(self.map:get(s, "dest_port")) or + ft.fmt_port(self.map:get(s, "src_dport")) + + if p then + return translatef("To %s, %s in %s", a, p, z) + else + return translatef("To %s in %s", a, z) + end +end + + +match = s:option(DummyValue, "match", translate("Match")) +match.rawhtml = true +match.width = "70%" +function match.cfgvalue(self, s) + return "%s
%s
%s
" % { + rule_proto_txt(self, s), + rule_src_txt(self, s), + rule_dest_txt(self, s) + } +end + +target = s:option(DummyValue, "target", translate("Action")) +target.rawhtml = true +target.width = "20%" +function target.cfgvalue(self, s) + local t = ft.fmt_target(self.map:get(s, "target"), self.map:get(s, "dest")) + local l = ft.fmt_limit(self.map:get(s, "limit"), + self.map:get(s, "limit_burst")) + + if l then + return translatef("%s and limit to %s", t, l) + else + return "%s" % t + end +end + +ft.opt_enabled(s, Flag, translate("Enable")).width = "1%" + + +-- +-- SNAT +-- + +s = m:section(TypedSection, "redirect", + translate("Source NAT"), + translate("Source NAT is a specific form of masquerading which allows \ + fine grained control over the source IP used for outgoing traffic, \ + for example to map multiple WAN addresses to internal subnets.")) +s.template = "cbi/tblsection" +s.addremove = true +s.anonymous = true +s.sortable = true +s.extedit = ds.build_url("admin/network/firewall/rules/%s") +s.template_addremove = "firewall/cbi_addsnat" + +function s.create(self, section) + created = TypedSection.create(self, section) +end + +function s.parse(self, ...) + TypedSection.parse(self, ...) + + local n = m:formvalue("_newsnat.name") + local s = m:formvalue("_newsnat.src") + local d = m:formvalue("_newsnat.dest") + local a = m:formvalue("_newsnat.dip") + local p = m:formvalue("_newsnat.dport") + local x = m:formvalue("_newsnat.submit") + + if x and a and #a > 0 then + created = TypedSection.create(self, section) + + self.map:set(created, "target", "SNAT") + self.map:set(created, "src", s) + self.map:set(created, "dest", d) + self.map:set(created, "proto", "all") + self.map:set(created, "src_dip", a) + self.map:set(created, "src_dport", p) + self.map:set(created, "name", n) + end + + if created then + m.uci:save("firewall") + luci.http.redirect(ds.build_url( + "admin/network/firewall/rules", created + )) + end +end + +function s.filter(self, sid) + return (self.map:get(sid, "target") == "SNAT") +end + +ft.opt_name(s, DummyValue, translate("Name")) + +match = s:option(DummyValue, "match", translate("Match")) +match.rawhtml = true +match.width = "70%" +function match.cfgvalue(self, s) + return "%s
%s
%s
" % { + rule_proto_txt(self, s), + rule_src_txt(self, s), + snat_dest_txt(self, s) + } +end + +snat = s:option(DummyValue, "via", translate("Action")) +snat.rawhtml = true +snat.width = "20%" +function snat.cfgvalue(self, s) + local a = ft.fmt_ip(self.map:get(s, "src_dip")) + local p = ft.fmt_port(self.map:get(s, "src_dport")) + + if a and p then + return translatef("Rewrite to source %s, %s", a, p) + else + return translatef("Rewrite to source %s", a or p) + end +end + +ft.opt_enabled(s, Flag, translate("Enable")).width = "1%" + + +return m diff --git a/Mi_Lua/luci/model/cbi/firewall/zone-details.lua b/Mi_Lua/luci/model/cbi/firewall/zone-details.lua new file mode 100644 index 0000000..c320d25 --- /dev/null +++ b/Mi_Lua/luci/model/cbi/firewall/zone-details.lua @@ -0,0 +1,243 @@ +--[[ +LuCI - Lua Configuration Interface + +Copyright 2008 Steven Barth +Copyright 2010-2011 Jo-Philipp Wich + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +$Id: zone-details.lua 8169 2012-01-09 05:48:27Z jow $ +]]-- + +local nw = require "luci.model.network" +local fw = require "luci.model.firewall" +local ds = require "luci.dispatcher" +local ut = require "luci.util" + +local m, p, i, v +local s, name, net, family, msrc, mdest, log, lim +local s2, out, inp + + +m = Map("firewall", translate("Firewall - Zone Settings")) +m.redirect = luci.dispatcher.build_url("admin/network/firewall/zones") + +fw.init(m.uci) +nw.init(m.uci) + + +local zone = fw:get_zone(arg[1]) +if not zone then + luci.http.redirect(dsp.build_url("admin/network/firewall/zones")) + return +else + m.title = "%s - %s" %{ + translate("Firewall - Zone Settings"), + translatef("Zone %q", zone:name() or "?") + } +end + + +s = m:section(NamedSection, zone.sid, "zone", + translatef("Zone %q", zone:name()), + translatef("This section defines common properties of %q. \ + The input and output options set the default \ + policies for traffic entering and leaving this zone while the \ + forward option describes the policy for forwarded traffic \ + between different networks within the zone. \ + Covered networks specifies which available networks are \ + member of this zone.", zone:name())) + +s.anonymous = true +s.addremove = false + +m.on_commit = function(map) + local zone = fw:get_zone(arg[1]) + if zone then + s.section = zone.sid + s2.section = zone.sid + end +end + + +s:tab("general", translate("General Settings")) +s:tab("advanced", translate("Advanced Settings")) + + +name = s:taboption("general", Value, "name", translate("Name")) +name.optional = false +name.forcewrite = true +name.datatype = "uciname" + +function name.write(self, section, value) + if zone:name() ~= value then + fw:rename_zone(zone:name(), value) + out.exclude = value + inp.exclude = value + end + + m.redirect = ds.build_url("admin/network/firewall/zones", value) + m.title = "%s - %s" %{ + translate("Firewall - Zone Settings"), + translatef("Zone %q", value or "?") + } +end + +p = { + s:taboption("general", ListValue, "input", translate("Input")), + s:taboption("general", ListValue, "output", translate("Output")), + s:taboption("general", ListValue, "forward", translate("Forward")) +} + +for i, v in ipairs(p) do + v:value("REJECT", translate("reject")) + v:value("DROP", translate("drop")) + v:value("ACCEPT", translate("accept")) +end + +s:taboption("general", Flag, "masq", translate("Masquerading")) +s:taboption("general", Flag, "mtu_fix", translate("MSS clamping")) + +net = s:taboption("general", Value, "network", translate("Covered networks")) +net.template = "cbi/network_netlist" +net.widget = "checkbox" +net.cast = "string" + +function net.formvalue(self, section) + return Value.formvalue(self, section) or "-" +end + +function net.cfgvalue(self, section) + return Value.cfgvalue(self, section) or name:cfgvalue(section) +end + +function net.write(self, section, value) + zone:clear_networks() + + local n + for n in ut.imatch(value) do + zone:add_network(n) + end +end + + +family = s:taboption("advanced", ListValue, "family", + translate("Restrict to address family")) + +family.rmempty = true +family:value("", translate("IPv4 and IPv6")) +family:value("ipv4", translate("IPv4 only")) +family:value("ipv6", translate("IPv6 only")) + +msrc = s:taboption("advanced", DynamicList, "masq_src", + translate("Restrict Masquerading to given source subnets")) + +msrc.optional = true +msrc.datatype = "list(neg(or(uciname,hostname,ip4addr)))" +msrc.placeholder = "0.0.0.0/0" +msrc:depends("family", "") +msrc:depends("family", "ipv4") + +mdest = s:taboption("advanced", DynamicList, "masq_dest", + translate("Restrict Masquerading to given destination subnets")) + +mdest.optional = true +mdest.datatype = "list(neg(or(uciname,hostname,ip4addr)))" +mdest.placeholder = "0.0.0.0/0" +mdest:depends("family", "") +mdest:depends("family", "ipv4") + +s:taboption("advanced", Flag, "conntrack", + translate("Force connection tracking")) + +log = s:taboption("advanced", Flag, "log", + translate("Enable logging on this zone")) + +log.rmempty = true +log.enabled = "1" + +lim = s:taboption("advanced", Value, "log_limit", + translate("Limit log messages")) + +lim.placeholder = "10/minute" +lim:depends("log", "1") + + +s2 = m:section(NamedSection, zone.sid, "fwd_out", + translate("Inter-Zone Forwarding"), + translatef("The options below control the forwarding policies between \ + this zone (%s) and other zones. Destination zones cover \ + forwarded traffic originating from %q. \ + Source zones match forwarded traffic from other zones \ + targeted at %q. The forwarding rule is \ + unidirectional, e.g. a forward from lan to wan does \ + not imply a permission to forward from wan to lan as well.", + zone:name(), zone:name(), zone:name() + + )) + +out = s2:option(Value, "out", + translate("Allow forward to destination zones:")) + +out.nocreate = true +out.widget = "checkbox" +out.exclude = zone:name() +out.template = "cbi/firewall_zonelist" + +inp = s2:option(Value, "in", + translate("Allow forward from source zones:")) + +inp.nocreate = true +inp.widget = "checkbox" +inp.exclude = zone:name() +inp.template = "cbi/firewall_zonelist" + +function out.cfgvalue(self, section) + local v = { } + local f + for _, f in ipairs(zone:get_forwardings_by("src")) do + v[#v+1] = f:dest() + end + return table.concat(v, " ") +end + +function inp.cfgvalue(self, section) + local v = { } + local f + for _, f in ipairs(zone:get_forwardings_by("dest")) do + v[#v+1] = f:src() + end + return v +end + +function out.formvalue(self, section) + return Value.formvalue(self, section) or "-" +end + +function inp.formvalue(self, section) + return Value.formvalue(self, section) or "-" +end + +function out.write(self, section, value) + zone:del_forwardings_by("src") + + local f + for f in ut.imatch(value) do + zone:add_forwarding_to(f) + end +end + +function inp.write(self, section, value) + zone:del_forwardings_by("dest") + + local f + for f in ut.imatch(value) do + zone:add_forwarding_from(f) + end +end + +return m diff --git a/Mi_Lua/luci/model/cbi/firewall/zones.lua b/Mi_Lua/luci/model/cbi/firewall/zones.lua new file mode 100644 index 0000000..de3b13f --- /dev/null +++ b/Mi_Lua/luci/model/cbi/firewall/zones.lua @@ -0,0 +1,88 @@ +--[[ +LuCI - Lua Configuration Interface + +Copyright 2008 Steven Barth + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +$Id: zones.lua 8108 2011-12-19 21:16:31Z jow $ +]]-- + +local ds = require "luci.dispatcher" +local fw = require "luci.model.firewall" + +local m, s, o, p, i, v + +m = Map("firewall", + translate("Firewall - Zone Settings"), + translate("The firewall creates zones over your network interfaces to control network traffic flow.")) + +fw.init(m.uci) + +s = m:section(TypedSection, "defaults", translate("General Settings")) +s.anonymous = true +s.addremove = false + +s:option(Flag, "syn_flood", translate("Enable SYN-flood protection")) + +o = s:option(Flag, "drop_invalid", translate("Drop invalid packets")) +o.default = o.disabled + +p = { + s:option(ListValue, "input", translate("Input")), + s:option(ListValue, "output", translate("Output")), + s:option(ListValue, "forward", translate("Forward")) +} + +for i, v in ipairs(p) do + v:value("REJECT", translate("reject")) + v:value("DROP", translate("drop")) + v:value("ACCEPT", translate("accept")) +end + + +s = m:section(TypedSection, "zone", translate("Zones")) +s.template = "cbi/tblsection" +s.anonymous = true +s.addremove = true +s.extedit = ds.build_url("admin", "network", "firewall", "zones", "%s") + +function s.create(self) + local z = fw:new_zone() + if z then + luci.http.redirect( + ds.build_url("admin", "network", "firewall", "zones", z.sid) + ) + end +end + +function s.remove(self, section) + return fw:del_zone(section) +end + +o = s:option(DummyValue, "_info", translate("Zone ⇒ Forwardings")) +o.template = "cbi/firewall_zoneforwards" +o.cfgvalue = function(self, section) + return self.map:get(section, "name") +end + +p = { + s:option(ListValue, "input", translate("Input")), + s:option(ListValue, "output", translate("Output")), + s:option(ListValue, "forward", translate("Forward")) +} + +for i, v in ipairs(p) do + v:value("REJECT", translate("reject")) + v:value("DROP", translate("drop")) + v:value("ACCEPT", translate("accept")) +end + +s:option(Flag, "masq", translate("Masquerading")) +s:option(Flag, "mtu_fix", translate("MSS clamping")) + +return m diff --git a/Mi_Lua/luci/model/firewall.lua b/Mi_Lua/luci/model/firewall.lua new file mode 100644 index 0000000..a9f6fdb --- /dev/null +++ b/Mi_Lua/luci/model/firewall.lua @@ -0,0 +1,582 @@ +--[[ +LuCI - Firewall model + +Copyright 2009 Jo-Philipp Wich + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +]]-- + +local type, pairs, ipairs, table, luci, math + = type, pairs, ipairs, table, luci, math + +local tpl = require "luci.template.parser" +local utl = require "luci.util" +local uci = require "luci.model.uci" + +module "luci.model.firewall" + + +local uci_r, uci_s + +function _valid_id(x) + return (x and #x > 0 and x:match("^[a-zA-Z0-9_]+$")) +end + +function _get(c, s, o) + return uci_r:get(c, s, o) +end + +function _set(c, s, o, v) + if v ~= nil then + if type(v) == "boolean" then v = v and "1" or "0" end + return uci_r:set(c, s, o, v) + else + return uci_r:delete(c, s, o) + end +end + + +function init(cursor) + uci_r = cursor or uci_r or uci.cursor() + uci_s = uci_r:substate() + + return _M +end + +function save(self, ...) + uci_r:save(...) + uci_r:load(...) +end + +function commit(self, ...) + uci_r:commit(...) + uci_r:load(...) +end + +function get_defaults() + return defaults() +end + +function new_zone(self) + local name = "newzone" + local count = 1 + + while self:get_zone(name) do + count = count + 1 + name = "newzone%d" % count + end + + return self:add_zone(name) +end + +function add_zone(self, n) + if _valid_id(n) and not self:get_zone(n) then + local d = defaults() + local z = uci_r:section("firewall", "zone", nil, { + name = n, + network = " ", + input = d:input() or "DROP", + forward = d:forward() or "DROP", + output = d:output() or "DROP" + }) + + return z and zone(z) + end +end + +function get_zone(self, n) + if uci_r:get("firewall", n) == "zone" then + return zone(n) + else + local z + uci_r:foreach("firewall", "zone", + function(s) + if n and s.name == n then + z = s['.name'] + return false + end + end) + return z and zone(z) + end +end + +function get_zones(self) + local zones = { } + local znl = { } + + uci_r:foreach("firewall", "zone", + function(s) + if s.name then + znl[s.name] = zone(s['.name']) + end + end) + + local z + for z in utl.kspairs(znl) do + zones[#zones+1] = znl[z] + end + + return zones +end + +function get_zone_by_network(self, net) + local z + + uci_r:foreach("firewall", "zone", + function(s) + if s.name and net then + local n + for n in utl.imatch(s.network or s.name) do + if n == net then + z = s['.name'] + return false + end + end + end + end) + + return z and zone(z) +end + +function del_zone(self, n) + local r = false + + if uci_r:get("firewall", n) == "zone" then + local z = uci_r:get("firewall", n, "name") + r = uci_r:delete("firewall", n) + n = z + else + uci_r:foreach("firewall", "zone", + function(s) + if n and s.name == n then + r = uci_r:delete("firewall", s['.name']) + return false + end + end) + end + + if r then + uci_r:foreach("firewall", "rule", + function(s) + if s.src == n or s.dest == n then + uci_r:delete("firewall", s['.name']) + end + end) + + uci_r:foreach("firewall", "redirect", + function(s) + if s.src == n or s.dest == n then + uci_r:delete("firewall", s['.name']) + end + end) + + uci_r:foreach("firewall", "forwarding", + function(s) + if s.src == n or s.dest == n then + uci_r:delete("firewall", s['.name']) + end + end) + end + + return r +end + +function rename_zone(self, old, new) + local r = false + + if _valid_id(new) and not self:get_zone(new) then + uci_r:foreach("firewall", "zone", + function(s) + if old and s.name == old then + if not s.network then + uci_r:set("firewall", s['.name'], "network", old) + end + uci_r:set("firewall", s['.name'], "name", new) + r = true + return false + end + end) + + if r then + uci_r:foreach("firewall", "rule", + function(s) + if s.src == old then + uci_r:set("firewall", s['.name'], "src", new) + end + if s.dest == old then + uci_r:set("firewall", s['.name'], "dest", new) + end + end) + + uci_r:foreach("firewall", "redirect", + function(s) + if s.src == old then + uci_r:set("firewall", s['.name'], "src", new) + end + if s.dest == old then + uci_r:set("firewall", s['.name'], "dest", new) + end + end) + + uci_r:foreach("firewall", "forwarding", + function(s) + if s.src == old then + uci_r:set("firewall", s['.name'], "src", new) + end + if s.dest == old then + uci_r:set("firewall", s['.name'], "dest", new) + end + end) + end + end + + return r +end + +function del_network(self, net) + local z + if net then + for _, z in ipairs(self:get_zones()) do + z:del_network(net) + end + end +end + + +defaults = utl.class() +function defaults.__init__(self) + uci_r:foreach("firewall", "defaults", + function(s) + self.sid = s['.name'] + return false + end) + + self.sid = self.sid or uci_r:section("firewall", "defaults", nil, { }) +end + +function defaults.get(self, opt) + return _get("firewall", self.sid, opt) +end + +function defaults.set(self, opt, val) + return _set("firewall", self.sid, opt, val) +end + +function defaults.syn_flood(self) + return (self:get("syn_flood") == "1") +end + +function defaults.drop_invalid(self) + return (self:get("drop_invalid") == "1") +end + +function defaults.input(self) + return self:get("input") or "DROP" +end + +function defaults.forward(self) + return self:get("forward") or "DROP" +end + +function defaults.output(self) + return self:get("output") or "DROP" +end + + +zone = utl.class() +function zone.__init__(self, z) + if uci_r:get("firewall", z) == "zone" then + self.sid = z + self.data = uci_r:get_all("firewall", z) + else + uci_r:foreach("firewall", "zone", + function(s) + if s.name == z then + self.sid = s['.name'] + self.data = s + return false + end + end) + end +end + +function zone.get(self, opt) + return _get("firewall", self.sid, opt) +end + +function zone.set(self, opt, val) + return _set("firewall", self.sid, opt, val) +end + +function zone.masq(self) + return (self:get("masq") == "1") +end + +function zone.name(self) + return self:get("name") +end + +function zone.network(self) + return self:get("network") +end + +function zone.input(self) + return self:get("input") or defaults():input() or "DROP" +end + +function zone.forward(self) + return self:get("forward") or defaults():forward() or "DROP" +end + +function zone.output(self) + return self:get("output") or defaults():output() or "DROP" +end + +function zone.add_network(self, net) + if uci_r:get("network", net) == "interface" then + local nets = { } + + local n + for n in utl.imatch(self:get("network") or self:get("name")) do + if n ~= net then + nets[#nets+1] = n + end + end + + nets[#nets+1] = net + + _M:del_network(net) + self:set("network", table.concat(nets, " ")) + end +end + +function zone.del_network(self, net) + local nets = { } + + local n + for n in utl.imatch(self:get("network") or self:get("name")) do + if n ~= net then + nets[#nets+1] = n + end + end + + if #nets > 0 then + self:set("network", table.concat(nets, " ")) + else + self:set("network", " ") + end +end + +function zone.get_networks(self) + local nets = { } + + local n + for n in utl.imatch(self:get("network") or self:get("name")) do + nets[#nets+1] = n + end + + return nets +end + +function zone.clear_networks(self) + self:set("network", " ") +end + +function zone.get_forwardings_by(self, what) + local name = self:name() + local forwards = { } + + uci_r:foreach("firewall", "forwarding", + function(s) + if s.src and s.dest and s[what] == name then + forwards[#forwards+1] = forwarding(s['.name']) + end + end) + + return forwards +end + +function zone.add_forwarding_to(self, dest) + local exist, forward + + for _, forward in ipairs(self:get_forwardings_by('src')) do + if forward:dest() == dest then + exist = true + break + end + end + + if not exist and dest ~= self:name() and _valid_id(dest) then + local s = uci_r:section("firewall", "forwarding", nil, { + src = self:name(), + dest = dest + }) + + return s and forwarding(s) + end +end + +function zone.add_forwarding_from(self, src) + local exist, forward + + for _, forward in ipairs(self:get_forwardings_by('dest')) do + if forward:src() == src then + exist = true + break + end + end + + if not exist and src ~= self:name() and _valid_id(src) then + local s = uci_r:section("firewall", "forwarding", nil, { + src = src, + dest = self:name() + }) + + return s and forwarding(s) + end +end + +function zone.del_forwardings_by(self, what) + local name = self:name() + + uci_r:delete_all("firewall", "forwarding", + function(s) + return (s.src and s.dest and s[what] == name) + end) +end + +function zone.add_redirect(self, options) + options = options or { } + options.src = self:name() + + local s = uci_r:section("firewall", "redirect", nil, options) + return s and redirect(s) +end + +function zone.add_rule(self, options) + options = options or { } + options.src = self:name() + + local s = uci_r:section("firewall", "rule", nil, options) + return s and rule(s) +end + +function zone.get_color(self) + if self and self:name() == "lan" then + return "#90f090" + elseif self and self:name() == "wan" then + return "#f09090" + elseif self then + math.randomseed(tpl.hash(self:name())) + + local r = math.random(128) + local g = math.random(128) + local min = 0 + local max = 128 + + if ( r + g ) < 128 then + min = 128 - r - g + else + max = 255 - r - g + end + + local b = min + math.floor( math.random() * ( max - min ) ) + + return "#%02x%02x%02x" % { 0xFF - r, 0xFF - g, 0xFF - b } + else + return "#eeeeee" + end +end + + +forwarding = utl.class() +function forwarding.__init__(self, f) + self.sid = f +end + +function forwarding.src(self) + return uci_r:get("firewall", self.sid, "src") +end + +function forwarding.dest(self) + return uci_r:get("firewall", self.sid, "dest") +end + +function forwarding.src_zone(self) + return zone(self:src()) +end + +function forwarding.dest_zone(self) + return zone(self:dest()) +end + + +rule = utl.class() +function rule.__init__(self, f) + self.sid = f +end + +function rule.get(self, opt) + return _get("firewall", self.sid, opt) +end + +function rule.set(self, opt, val) + return _set("firewall", self.sid, opt, val) +end + +function rule.src(self) + return uci_r:get("firewall", self.sid, "src") +end + +function rule.dest(self) + return uci_r:get("firewall", self.sid, "dest") +end + +function rule.src_zone(self) + return zone(self:src()) +end + +function rule.dest_zone(self) + return zone(self:dest()) +end + + +redirect = utl.class() +function redirect.__init__(self, f) + self.sid = f +end + +function redirect.get(self, opt) + return _get("firewall", self.sid, opt) +end + +function redirect.set(self, opt, val) + return _set("firewall", self.sid, opt, val) +end + +function redirect.src(self) + return uci_r:get("firewall", self.sid, "src") +end + +function redirect.dest(self) + return uci_r:get("firewall", self.sid, "dest") +end + +function redirect.src_zone(self) + return zone(self:src()) +end + +function redirect.dest_zone(self) + return zone(self:dest()) +end diff --git a/Mi_Lua/luci/model/ipkg.lua b/Mi_Lua/luci/model/ipkg.lua new file mode 100644 index 0000000..c927e71 --- /dev/null +++ b/Mi_Lua/luci/model/ipkg.lua @@ -0,0 +1,239 @@ +--[[ +LuCI - Lua Configuration Interface + +(c) 2008-2011 Jo-Philipp Wich +(c) 2008 Steven Barth + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +]]-- + +local os = require "os" +local io = require "io" +local fs = require "nixio.fs" +local util = require "luci.util" + +local type = type +local pairs = pairs +local error = error +local table = table + +local ipkg = "opkg --force-removal-of-dependent-packages --force-overwrite --nocase" +local icfg = "/etc/opkg.conf" + +--- LuCI OPKG call abstraction library +module "luci.model.ipkg" + + +-- Internal action function +local function _action(cmd, ...) + local pkg = "" + for k, v in pairs({...}) do + pkg = pkg .. " '" .. v:gsub("'", "") .. "'" + end + + local c = "%s %s %s >/tmp/opkg.stdout 2>/tmp/opkg.stderr" %{ ipkg, cmd, pkg } + local r = os.execute(c) + local e = fs.readfile("/tmp/opkg.stderr") + local o = fs.readfile("/tmp/opkg.stdout") + + fs.unlink("/tmp/opkg.stderr") + fs.unlink("/tmp/opkg.stdout") + + return r, o or "", e or "" +end + +-- Internal parser function +local function _parselist(rawdata) + if type(rawdata) ~= "function" then + error("OPKG: Invalid rawdata given") + end + + local data = {} + local c = {} + local l = nil + + for line in rawdata do + if line:sub(1, 1) ~= " " then + local key, val = line:match("(.-): ?(.*)%s*") + + if key and val then + if key == "Package" then + c = {Package = val} + data[val] = c + elseif key == "Status" then + c.Status = {} + for j in val:gmatch("([^ ]+)") do + c.Status[j] = true + end + else + c[key] = val + end + l = key + end + else + -- Multi-line field + c[l] = c[l] .. "\n" .. line + end + end + + return data +end + +-- Internal lookup function +local function _lookup(act, pkg) + local cmd = ipkg .. " " .. act + if pkg then + cmd = cmd .. " '" .. pkg:gsub("'", "") .. "'" + end + + -- OPKG sometimes kills the whole machine because it sucks + -- Therefore we have to use a sucky approach too and use + -- tmpfiles instead of directly reading the output + local tmpfile = os.tmpname() + os.execute(cmd .. (" >%s 2>/dev/null" % tmpfile)) + + local data = _parselist(io.lines(tmpfile)) + os.remove(tmpfile) + return data +end + + +--- Return information about installed and available packages. +-- @param pkg Limit output to a (set of) packages +-- @return Table containing package information +function info(pkg) + return _lookup("info", pkg) +end + +--- Return the package status of one or more packages. +-- @param pkg Limit output to a (set of) packages +-- @return Table containing package status information +function status(pkg) + return _lookup("status", pkg) +end + +--- Install one or more packages. +-- @param ... List of packages to install +-- @return Boolean indicating the status of the action +-- @return OPKG return code, STDOUT and STDERR +function install(...) + return _action("install", ...) +end + +--- Determine whether a given package is installed. +-- @param pkg Package +-- @return Boolean +function installed(pkg) + local p = status(pkg)[pkg] + return (p and p.Status and p.Status.installed) +end + +--- Remove one or more packages. +-- @param ... List of packages to install +-- @return Boolean indicating the status of the action +-- @return OPKG return code, STDOUT and STDERR +function remove(...) + return _action("remove", ...) +end + +--- Update package lists. +-- @return Boolean indicating the status of the action +-- @return OPKG return code, STDOUT and STDERR +function update() + return _action("update") +end + +--- Upgrades all installed packages. +-- @return Boolean indicating the status of the action +-- @return OPKG return code, STDOUT and STDERR +function upgrade() + return _action("upgrade") +end + +-- List helper +function _list(action, pat, cb) + local fd = io.popen(ipkg .. " " .. action .. + (pat and (" '%s'" % pat:gsub("'", "")) or "")) + + if fd then + local name, version, desc + while true do + local line = fd:read("*l") + if not line then break end + + name, version, desc = line:match("^(.-) %- (.-) %- (.+)") + + if not name then + name, version = line:match("^(.-) %- (.+)") + desc = "" + end + + cb(name, version, desc) + + name = nil + version = nil + desc = nil + end + + fd:close() + end +end + +--- List all packages known to opkg. +-- @param pat Only find packages matching this pattern, nil lists all packages +-- @param cb Callback function invoked for each package, receives name, version and description as arguments +-- @return nothing +function list_all(pat, cb) + _list("list", pat, cb) +end + +--- List installed packages. +-- @param pat Only find packages matching this pattern, nil lists all packages +-- @param cb Callback function invoked for each package, receives name, version and description as arguments +-- @return nothing +function list_installed(pat, cb) + _list("list_installed", pat, cb) +end + +--- Find packages that match the given pattern. +-- @param pat Find packages whose names or descriptions match this pattern, nil results in zero results +-- @param cb Callback function invoked for each patckage, receives name, version and description as arguments +-- @return nothing +function find(pat, cb) + _list("find", pat, cb) +end + + +--- Determines the overlay root used by opkg. +-- @return String containing the directory path of the overlay root. +function overlay_root() + local od = "/" + local fd = io.open(icfg, "r") + + if fd then + local ln + + repeat + ln = fd:read("*l") + if ln and ln:match("^%s*option%s+overlay_root%s+") then + od = ln:match("^%s*option%s+overlay_root%s+(%S+)") + + local s = fs.stat(od) + if not s or s.type ~= "dir" then + od = "/" + end + + break + end + until not ln + + fd:close() + end + + return od +end diff --git a/Mi_Lua/luci/model/network.lua b/Mi_Lua/luci/model/network.lua new file mode 100644 index 0000000..9a9a5f4 --- /dev/null +++ b/Mi_Lua/luci/model/network.lua @@ -0,0 +1,1639 @@ +--[[ +LuCI - Network model + +Copyright 2009-2010 Jo-Philipp Wich + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +]]-- + +local type, next, pairs, ipairs, loadfile, table + = type, next, pairs, ipairs, loadfile, table + +local tonumber, tostring, math = tonumber, tostring, math + +local require = require + +local bus = require "ubus" +local nxo = require "nixio" +local nfs = require "nixio.fs" +local ipc = require "luci.ip" +local sys = require "luci.sys" +local utl = require "luci.util" +local dsp = require "luci.dispatcher" +local uci = require "luci.model.uci" +local lng = require "luci.i18n" + +module "luci.model.network" + + +IFACE_PATTERNS_VIRTUAL = { } +IFACE_PATTERNS_IGNORE = { "^wmaster%d", "^wifi%d", "^hwsim%d", "^imq%d", "^ifb%d", "^mon%.wlan%d", "^sit%d", "^gre%d", "^lo$" } +IFACE_PATTERNS_WIRELESS = { "^wlan%d", "^wl%d", "^ath%d", "^%w+%.network%d" } + + +protocol = utl.class() + +local _protocols = { } + +local _interfaces, _bridge, _switch, _tunnel +local _ubus, _ubusnetcache, _ubusdevcache +local _uci_real, _uci_state + +function _filter(c, s, o, r) + local val = _uci_real:get(c, s, o) + if val then + local l = { } + if type(val) == "string" then + for val in val:gmatch("%S+") do + if val ~= r then + l[#l+1] = val + end + end + if #l > 0 then + _uci_real:set(c, s, o, table.concat(l, " ")) + else + _uci_real:delete(c, s, o) + end + elseif type(val) == "table" then + for _, val in ipairs(val) do + if val ~= r then + l[#l+1] = val + end + end + if #l > 0 then + _uci_real:set(c, s, o, l) + else + _uci_real:delete(c, s, o) + end + end + end +end + +function _append(c, s, o, a) + local val = _uci_real:get(c, s, o) or "" + if type(val) == "string" then + local l = { } + for val in val:gmatch("%S+") do + if val ~= a then + l[#l+1] = val + end + end + l[#l+1] = a + _uci_real:set(c, s, o, table.concat(l, " ")) + elseif type(val) == "table" then + local l = { } + for _, val in ipairs(val) do + if val ~= a then + l[#l+1] = val + end + end + l[#l+1] = a + _uci_real:set(c, s, o, l) + end +end + +function _stror(s1, s2) + if not s1 or #s1 == 0 then + return s2 and #s2 > 0 and s2 + else + return s1 + end +end + +function _get(c, s, o) + return _uci_real:get(c, s, o) +end + +function _set(c, s, o, v) + if v ~= nil then + if type(v) == "boolean" then v = v and "1" or "0" end + return _uci_real:set(c, s, o, v) + else + return _uci_real:delete(c, s, o) + end +end + +function _wifi_iface(x) + local _, p + for _, p in ipairs(IFACE_PATTERNS_WIRELESS) do + if x:match(p) then + return true + end + end + return false +end + +function _wifi_lookup(ifn) + -- got a radio#.network# pseudo iface, locate the corresponding section + local radio, ifnidx = ifn:match("^(%w+)%.network(%d+)$") + if radio and ifnidx then + local sid = nil + local num = 0 + + ifnidx = tonumber(ifnidx) + _uci_real:foreach("wireless", "wifi-iface", + function(s) + if s.device == radio then + num = num + 1 + if num == ifnidx then + sid = s['.name'] + return false + end + end + end) + + return sid + + -- looks like wifi, try to locate the section via state vars + elseif _wifi_iface(ifn) then + local sid = nil + + _uci_state:foreach("wireless", "wifi-iface", + function(s) + if s.ifname == ifn then + sid = s['.name'] + return false + end + end) + + return sid + end +end + +function _iface_virtual(x) + local _, p + for _, p in ipairs(IFACE_PATTERNS_VIRTUAL) do + if x:match(p) then + return true + end + end + return false +end + +function _iface_ignore(x) + local _, p + for _, p in ipairs(IFACE_PATTERNS_IGNORE) do + if x:match(p) then + return true + end + end + return _iface_virtual(x) +end + + +function init(cursor) + _uci_real = cursor or _uci_real or uci.cursor() + _uci_state = _uci_real:substate() + + _interfaces = { } + _bridge = { } + _switch = { } + _tunnel = { } + + _ubus = bus.connect() + _ubusnetcache = { } + _ubusdevcache = { } + + -- read interface information + local n, i + for n, i in ipairs(nxo.getifaddrs()) do + local name = i.name:match("[^:]+") + local prnt = name:match("^([^%.]+)%.") + + if _iface_virtual(name) then + _tunnel[name] = true + end + + if _tunnel[name] or not _iface_ignore(name) then + _interfaces[name] = _interfaces[name] or { + idx = i.ifindex or n, + name = name, + rawname = i.name, + flags = { }, + ipaddrs = { }, + ip6addrs = { } + } + + if prnt then + _switch[name] = true + _switch[prnt] = true + end + + if i.family == "packet" then + _interfaces[name].flags = i.flags + _interfaces[name].stats = i.data + _interfaces[name].macaddr = i.addr + elseif i.family == "inet" then + _interfaces[name].ipaddrs[#_interfaces[name].ipaddrs+1] = ipc.IPv4(i.addr, i.netmask) + elseif i.family == "inet6" then + _interfaces[name].ip6addrs[#_interfaces[name].ip6addrs+1] = ipc.IPv6(i.addr, i.netmask) + end + end + end + + -- read bridge informaton + local b, l + for l in utl.execi("brctl show") do + if not l:match("STP") then + local r = utl.split(l, "%s+", nil, true) + if #r == 4 then + b = { + name = r[1], + id = r[2], + stp = r[3] == "yes", + ifnames = { _interfaces[r[4]] } + } + if b.ifnames[1] then + b.ifnames[1].bridge = b + end + _bridge[r[1]] = b + elseif b then + b.ifnames[#b.ifnames+1] = _interfaces[r[2]] + b.ifnames[#b.ifnames].bridge = b + end + end + end + + return _M +end + +function save(self, ...) + _uci_real:save(...) + _uci_real:load(...) +end + +function commit(self, ...) + _uci_real:commit(...) + _uci_real:load(...) +end + +function ifnameof(self, x) + if utl.instanceof(x, interface) then + return x:name() + elseif utl.instanceof(x, protocol) then + return x:ifname() + elseif type(x) == "string" then + return x:match("^[^:]+") + end +end + +function get_protocol(self, protoname, netname) + local v = _protocols[protoname] + if v then + return v(netname or "__dummy__") + end +end + +function get_protocols(self) + local p = { } + local _, v + for _, v in ipairs(_protocols) do + p[#p+1] = v("__dummy__") + end + return p +end + +function register_protocol(self, protoname) + local proto = utl.class(protocol) + + function proto.__init__(self, name) + self.sid = name + end + + function proto.proto(self) + return protoname + end + + _protocols[#_protocols+1] = proto + _protocols[protoname] = proto + + return proto +end + +function register_pattern_virtual(self, pat) + IFACE_PATTERNS_VIRTUAL[#IFACE_PATTERNS_VIRTUAL+1] = pat +end + + +function has_ipv6(self) + return nfs.access("/proc/net/ipv6_route") +end + +function add_network(self, n, options) + local oldnet = self:get_network(n) + if n and #n > 0 and n:match("^[a-zA-Z0-9_]+$") and not oldnet then + if _uci_real:section("network", "interface", n, options) then + return network(n) + end + elseif oldnet and oldnet:is_empty() then + if options then + local k, v + for k, v in pairs(options) do + oldnet:set(k, v) + end + end + return oldnet + end +end + +function get_network(self, n) + if n and _uci_real:get("network", n) == "interface" then + return network(n) + end +end + +function get_networks(self) + local nets = { } + local nls = { } + + _uci_real:foreach("network", "interface", + function(s) + nls[s['.name']] = network(s['.name']) + end) + + local n + for n in utl.kspairs(nls) do + nets[#nets+1] = nls[n] + end + + return nets +end + +function del_network(self, n) + local r = _uci_real:delete("network", n) + if r then + _uci_real:delete_all("network", "alias", + function(s) return (s.interface == n) end) + + _uci_real:delete_all("network", "route", + function(s) return (s.interface == n) end) + + _uci_real:delete_all("network", "route6", + function(s) return (s.interface == n) end) + + _uci_real:foreach("wireless", "wifi-iface", + function(s) + local net + local rest = { } + for net in utl.imatch(s.network) do + if net ~= n then + rest[#rest+1] = net + end + end + if #rest > 0 then + _uci_real:set("wireless", s['.name'], "network", + table.concat(rest, " ")) + else + _uci_real:delete("wireless", s['.name'], "network") + end + end) + end + return r +end + +function rename_network(self, old, new) + local r + if new and #new > 0 and new:match("^[a-zA-Z0-9_]+$") and not self:get_network(new) then + r = _uci_real:section("network", "interface", new, _uci_real:get_all("network", old)) + + if r then + _uci_real:foreach("network", "alias", + function(s) + if s.interface == old then + _uci_real:set("network", s['.name'], "interface", new) + end + end) + + _uci_real:foreach("network", "route", + function(s) + if s.interface == old then + _uci_real:set("network", s['.name'], "interface", new) + end + end) + + _uci_real:foreach("network", "route6", + function(s) + if s.interface == old then + _uci_real:set("network", s['.name'], "interface", new) + end + end) + + _uci_real:foreach("wireless", "wifi-iface", + function(s) + local net + local list = { } + for net in utl.imatch(s.network) do + if net == old then + list[#list+1] = new + else + list[#list+1] = net + end + end + if #list > 0 then + _uci_real:set("wireless", s['.name'], "network", + table.concat(list, " ")) + end + end) + + _uci_real:delete("network", old) + end + end + return r or false +end + +function get_interface(self, i) + if _interfaces[i] or _wifi_iface(i) then + return interface(i) + else + local ifc + local num = { } + _uci_real:foreach("wireless", "wifi-iface", + function(s) + if s.device then + num[s.device] = num[s.device] and num[s.device] + 1 or 1 + if s['.name'] == i then + ifc = interface( + "%s.network%d" %{s.device, num[s.device] }) + return false + end + end + end) + return ifc + end +end + +function get_interfaces(self) + local iface + local ifaces = { } + local seen = { } + local nfs = { } + local baseof = { } + + -- find normal interfaces + _uci_real:foreach("network", "interface", + function(s) + for iface in utl.imatch(s.ifname) do + if not _iface_ignore(iface) and not _wifi_iface(iface) then + seen[iface] = true + nfs[iface] = interface(iface) + end + end + end) + + for iface in utl.kspairs(_interfaces) do + if not (seen[iface] or _iface_ignore(iface) or _wifi_iface(iface)) then + nfs[iface] = interface(iface) + end + end + + -- find vlan interfaces + _uci_real:foreach("network", "switch_vlan", + function(s) + if not s.device then + return + end + + local base = baseof[s.device] + if not base then + if not s.device:match("^eth%d") then + local l + for l in utl.execi("swconfig dev %q help 2>/dev/null" % s.device) do + if not base then + base = l:match("^%w+: (%w+)") + end + end + if not base or not base:match("^eth%d") then + base = "eth0" + end + else + base = s.device + end + baseof[s.device] = base + end + + local vid = tonumber(s.vid or s.vlan) + if vid ~= nil and vid >= 0 and vid <= 4095 then + local iface = "%s.%d" %{ base, vid } + if not seen[iface] then + seen[iface] = true + nfs[iface] = interface(iface) + end + end + end) + + for iface in utl.kspairs(nfs) do + ifaces[#ifaces+1] = nfs[iface] + end + + -- find wifi interfaces + local num = { } + local wfs = { } + _uci_real:foreach("wireless", "wifi-iface", + function(s) + if s.device then + num[s.device] = num[s.device] and num[s.device] + 1 or 1 + local i = "%s.network%d" %{ s.device, num[s.device] } + wfs[i] = interface(i) + end + end) + + for iface in utl.kspairs(wfs) do + ifaces[#ifaces+1] = wfs[iface] + end + + return ifaces +end + +function ignore_interface(self, x) + return _iface_ignore(x) +end + +function get_wifidev(self, dev) + if _uci_real:get("wireless", dev) == "wifi-device" then + return wifidev(dev) + end +end + +function get_wifidevs(self) + local devs = { } + local wfd = { } + + _uci_real:foreach("wireless", "wifi-device", + function(s) wfd[#wfd+1] = s['.name'] end) + + local dev + for _, dev in utl.vspairs(wfd) do + devs[#devs+1] = wifidev(dev) + end + + return devs +end + +function get_wifinet(self, net) + local wnet = _wifi_lookup(net) + if wnet then + return wifinet(wnet) + end +end + +function add_wifinet(self, net, options) + if type(options) == "table" and options.device and + _uci_real:get("wireless", options.device) == "wifi-device" + then + local wnet = _uci_real:section("wireless", "wifi-iface", nil, options) + return wifinet(wnet) + end +end + +function del_wifinet(self, net) + local wnet = _wifi_lookup(net) + if wnet then + _uci_real:delete("wireless", wnet) + return true + end + return false +end + +function get_status_by_route(self, addr, mask) + local _, object + for _, object in ipairs(_ubus:objects()) do + local net = object:match("^network%.interface%.(.+)") + if net then + local s = _ubus:call(object, "status", {}) + if s and s.route then + local rt + for _, rt in ipairs(s.route) do + if rt.target == addr and rt.mask == mask then + return net, s + end + end + end + end + end +end + +function get_status_by_address(self, addr) + local _, object + for _, object in ipairs(_ubus:objects()) do + local net = object:match("^network%.interface%.(.+)") + if net then + local s = _ubus:call(object, "status", {}) + if s and s['ipv4-address'] then + local a + for _, a in ipairs(s['ipv4-address']) do + if a.address == addr then + return net, s + end + end + end + if s and s['ipv6-address'] then + local a + for _, a in ipairs(s['ipv6-address']) do + if a.address == addr then + return net, s + end + end + end + end + end +end + +function get_wannet(self) + local net = self:get_status_by_route("0.0.0.0", 0) + return net and network(net) +end + +function get_wandev(self) + local _, stat = self:get_status_by_route("0.0.0.0", 0) + return stat and interface(stat.l3_device or stat.device) +end + +function get_wan6net(self) + local net = self:get_status_by_route("::", 0) + return net and network(net) +end + +function get_wan6dev(self) + local _, stat = self:get_status_by_route("::", 0) + return stat and interface(stat.l3_device or stat.device) +end + + +function network(name, proto) + if name then + local p = proto or _uci_real:get("network", name, "proto") + local c = p and _protocols[p] or protocol + return c(name) + end +end + +function protocol.__init__(self, name) + self.sid = name +end + +function protocol._get(self, opt) + local v = _uci_real:get("network", self.sid, opt) + if type(v) == "table" then + return table.concat(v, " ") + end + return v or "" +end + +function protocol._ubus(self, field) + if not _ubusnetcache[self.sid] then + _ubusnetcache[self.sid] = _ubus:call("network.interface.%s" % self.sid, + "status", { }) + end + if _ubusnetcache[self.sid] and field then + return _ubusnetcache[self.sid][field] + end + return _ubusnetcache[self.sid] +end + +function protocol.get(self, opt) + return _get("network", self.sid, opt) +end + +function protocol.set(self, opt, val) + return _set("network", self.sid, opt, val) +end + +function protocol.ifname(self) + local ifname + if self:is_floating() then + ifname = self:_ubus("l3_device") + else + ifname = self:_ubus("device") + end + if not ifname then + local num = { } + _uci_real:foreach("wireless", "wifi-iface", + function(s) + if s.device then + num[s.device] = num[s.device] + and num[s.device] + 1 or 1 + + local net + for net in utl.imatch(s.network) do + if net == self.sid then + ifname = "%s.network%d" %{ s.device, num[s.device] } + return false + end + end + end + end) + end + return ifname +end + +function protocol.proto(self) + return "none" +end + +function protocol.get_i18n(self) + local p = self:proto() + if p == "none" then + return lng.translate("Unmanaged") + elseif p == "static" then + return lng.translate("Static address") + elseif p == "dhcp" then + return lng.translate("DHCP client") + else + return lng.translate("Unknown") + end +end + +function protocol.type(self) + return self:_get("type") +end + +function protocol.name(self) + return self.sid +end + +function protocol.uptime(self) + return self:_ubus("uptime") or 0 +end + +function protocol.expires(self) + local a = tonumber(_uci_state:get("network", self.sid, "lease_acquired")) + local l = tonumber(_uci_state:get("network", self.sid, "lease_lifetime")) + if a and l then + l = l - (nxo.sysinfo().uptime - a) + return l > 0 and l or 0 + end + return -1 +end + +function protocol.metric(self) + return tonumber(_uci_state:get("network", self.sid, "metric")) or 0 +end + +function protocol.ipaddr(self) + local addrs = self:_ubus("ipv4-address") + return addrs and #addrs > 0 and addrs[1].address +end + +function protocol.netmask(self) + local addrs = self:_ubus("ipv4-address") + return addrs and #addrs > 0 and + ipc.IPv4("0.0.0.0/%d" % addrs[1].mask):mask():string() +end + +function protocol.gwaddr(self) + local _, route + for _, route in ipairs(self:_ubus("route") or { }) do + if route.target == "0.0.0.0" and route.mask == 0 then + return route.nexthop + end + end +end + +function protocol.dnsaddrs(self) + local dns = { } + local _, addr + for _, addr in ipairs(self:_ubus("dns-server") or { }) do + if not addr:match(":") then + dns[#dns+1] = addr + end + end + return dns +end + +function protocol.ip6addr(self) + local addrs = self:_ubus("ipv6-address") + if addrs and #addrs > 0 then + return "%s/%d" %{ addrs[1].address, addrs[1].mask } + else + addrs = self:_ubus("ipv6-prefix-assignment") + if addrs and #addrs > 0 then + return "%s/%d" %{ addrs[1].address, addrs[1].mask } + end + end +end + +function protocol.gw6addr(self) + local _, route + for _, route in ipairs(self:_ubus("route") or { }) do + if route.target == "::" and route.mask == 0 then + return ipc.IPv6(route.nexthop):string() + end + end +end + +function protocol.dns6addrs(self) + local dns = { } + local _, addr + for _, addr in ipairs(self:_ubus("dns-server") or { }) do + if addr:match(":") then + dns[#dns+1] = addr + end + end + return dns +end + +function protocol.is_bridge(self) + return (not self:is_virtual() and self:type() == "bridge") +end + +function protocol.opkg_package(self) + return nil +end + +function protocol.is_installed(self) + return true +end + +function protocol.is_virtual(self) + return false +end + +function protocol.is_floating(self) + return false +end + +function protocol.is_empty(self) + if self:is_floating() then + return false + else + local rv = true + + if (self:_get("ifname") or ""):match("%S+") then + rv = false + end + + _uci_real:foreach("wireless", "wifi-iface", + function(s) + local n + for n in utl.imatch(s.network) do + if n == self.sid then + rv = false + return false + end + end + end) + + return rv + end +end + +function protocol.add_interface(self, ifname) + ifname = _M:ifnameof(ifname) + if ifname and not self:is_floating() then + -- if its a wifi interface, change its network option + local wif = _wifi_lookup(ifname) + if wif then + _append("wireless", wif, "network", self.sid) + + -- add iface to our iface list + else + _append("network", self.sid, "ifname", ifname) + end + end +end + +function protocol.del_interface(self, ifname) + ifname = _M:ifnameof(ifname) + if ifname and not self:is_floating() then + -- if its a wireless interface, clear its network option + local wif = _wifi_lookup(ifname) + if wif then _filter("wireless", wif, "network", self.sid) end + + -- remove the interface + _filter("network", self.sid, "ifname", ifname) + end +end + +function protocol.get_interface(self) + if self:is_virtual() then + _tunnel[self:proto() .. "-" .. self.sid] = true + return interface(self:proto() .. "-" .. self.sid, self) + elseif self:is_bridge() then + _bridge["br-" .. self.sid] = true + return interface("br-" .. self.sid, self) + else + local ifn = nil + local num = { } + for ifn in utl.imatch(_uci_real:get("network", self.sid, "ifname")) do + ifn = ifn:match("^[^:/]+") + return ifn and interface(ifn, self) + end + ifn = nil + _uci_real:foreach("wireless", "wifi-iface", + function(s) + if s.device then + num[s.device] = num[s.device] and num[s.device] + 1 or 1 + + local net + for net in utl.imatch(s.network) do + if net == self.sid then + ifn = "%s.network%d" %{ s.device, num[s.device] } + return false + end + end + end + end) + return ifn and interface(ifn, self) + end +end + +function protocol.get_interfaces(self) + if self:is_bridge() or (self:is_virtual() and not self:is_floating()) then + local ifaces = { } + + local ifn + local nfs = { } + for ifn in utl.imatch(self:get("ifname")) do + ifn = ifn:match("^[^:/]+") + nfs[ifn] = interface(ifn, self) + end + + for ifn in utl.kspairs(nfs) do + ifaces[#ifaces+1] = nfs[ifn] + end + + local num = { } + local wfs = { } + _uci_real:foreach("wireless", "wifi-iface", + function(s) + if s.device then + num[s.device] = num[s.device] and num[s.device] + 1 or 1 + + local net + for net in utl.imatch(s.network) do + if net == self.sid then + ifn = "%s.network%d" %{ s.device, num[s.device] } + wfs[ifn] = interface(ifn, self) + end + end + end + end) + + for ifn in utl.kspairs(wfs) do + ifaces[#ifaces+1] = wfs[ifn] + end + + return ifaces + end +end + +function protocol.contains_interface(self, ifname) + ifname = _M:ifnameof(ifname) + if not ifname then + return false + elseif self:is_virtual() and self:proto() .. "-" .. self.sid == ifname then + return true + elseif self:is_bridge() and "br-" .. self.sid == ifname then + return true + else + local ifn + for ifn in utl.imatch(self:get("ifname")) do + ifn = ifn:match("[^:]+") + if ifn == ifname then + return true + end + end + + local wif = _wifi_lookup(ifname) + if wif then + local n + for n in utl.imatch(_uci_real:get("wireless", wif, "network")) do + if n == self.sid then + return true + end + end + end + end + + return false +end + +function protocol.adminlink(self) + return dsp.build_url("admin", "network", "network", self.sid) +end + +function protocol.get_option_value(self,name) + return self:_get(name) +end + +function protocol.status(self) + local iface = uci.cursor_state():get_all("network", self.sid) + local proto = iface["proto"] + local ifname = iface["ifname"] + local device = iface["device"] + local up = tonumber(iface["up"]) + if proto == "pppoe" then + if device == nil then + return "down" + end + if up == nil then + return "connection" + end + if up == 1 then + return "up" + end + elseif proto == "3g" then + if device ~= ifname and up==nil then + return "down" + end + if device == ifname and up == nil then + return "connection" + end + if up == 1 then + return "up" + end + elseif proto == "static" then + if up == nil then + return "down" + end + if up == 1 then + return "up" + end + elseif proto == "dhcp" then + if up == nil then + return "down" + end + if up == 1 then + return "up" + end + end + return "unkown" +end + +interface = utl.class() + +function interface.__init__(self, ifname, network) + local wif = _wifi_lookup(ifname) + if wif then + self.wif = wifinet(wif) + self.ifname = _uci_state:get("wireless", wif, "ifname") + end + + self.ifname = self.ifname or ifname + self.dev = _interfaces[self.ifname] + self.network = network +end + +function interface._ubus(self, field) + if not _ubusdevcache[self.ifname] then + _ubusdevcache[self.ifname] = _ubus:call("network.device", "status", + { name = self.ifname }) + end + if _ubusdevcache[self.ifname] and field then + return _ubusdevcache[self.ifname][field] + end + return _ubusdevcache[self.ifname] +end + +function interface.name(self) + return self.wif and self.wif:ifname() or self.ifname +end + +function interface.mac(self) + return (self:_ubus("macaddr") or "00:00:00:00:00:00"):upper() +end + +function interface.ipaddrs(self) + return self.dev and self.dev.ipaddrs or { } +end + +function interface.ip6addrs(self) + return self.dev and self.dev.ip6addrs or { } +end + +function interface.type(self) + if self.wif or _wifi_iface(self.ifname) then + return "wifi" + elseif _bridge[self.ifname] then + return "bridge" + elseif _tunnel[self.ifname] then + return "tunnel" + elseif self.ifname:match("%.") then + return "vlan" + elseif _switch[self.ifname] then + return "switch" + else + return "ethernet" + end +end + +function interface.shortname(self) + if self.wif then + return "%s %q" %{ + self.wif:active_mode(), + self.wif:active_ssid() or self.wif:active_bssid() + } + else + return self.ifname + end +end + +function interface.get_i18n(self) + if self.wif then + return "%s: %s %q" %{ + lng.translate("Wireless Network"), + self.wif:active_mode(), + self.wif:active_ssid() or self.wif:active_bssid() + } + else + return "%s: %q" %{ self:get_type_i18n(), self:name() } + end +end + +function interface.get_type_i18n(self) + local x = self:type() + if x == "wifi" then + return lng.translate("Wireless Adapter") + elseif x == "bridge" then + return lng.translate("Bridge") + elseif x == "switch" then + return lng.translate("Ethernet Switch") + elseif x == "vlan" then + return lng.translate("VLAN Interface") + elseif x == "tunnel" then + return lng.translate("Tunnel Interface") + else + return lng.translate("Ethernet Adapter") + end +end + +function interface.adminlink(self) + if self.wif then + return self.wif:adminlink() + end +end + +function interface.ports(self) + local members = self:_ubus("bridge-members") + if members then + local _, iface + local ifaces = { } + for _, iface in ipairs(members) do + ifaces[#ifaces+1] = interface(iface) + end + end +end + +function interface.bridge_id(self) + if self.br then + return self.br.id + else + return nil + end +end + +function interface.bridge_stp(self) + if self.br then + return self.br.stp + else + return false + end +end + +function interface.is_up(self) + if self.wif then + return self.wif:is_up() + else + return self:_ubus("up") or false + end +end + +function interface.is_bridge(self) + return (self:type() == "bridge") +end + +function interface.is_bridgeport(self) + return self.dev and self.dev.bridge and true or false +end + +function interface.tx_bytes(self) + local stat = self:_ubus("statistics") + return stat and stat.tx_bytes or 0 +end + +function interface.rx_bytes(self) + local stat = self:_ubus("statistics") + return stat and stat.rx_bytes or 0 +end + +function interface.tx_packets(self) + local stat = self:_ubus("statistics") + return stat and stat.tx_packets or 0 +end + +function interface.rx_packets(self) + local stat = self:_ubus("statistics") + return stat and stat.rx_packets or 0 +end + +function interface.get_network(self) + return self:get_networks()[1] +end + +function interface.get_networks(self) + if not self.networks then + local nets = { } + local _, net + for _, net in ipairs(_M:get_networks()) do + if net:contains_interface(self.ifname) or + net:ifname() == self.ifname + then + nets[#nets+1] = net + end + end + table.sort(nets, function(a, b) return a.sid < b.sid end) + self.networks = nets + return nets + else + return self.networks + end +end + +function interface.get_wifinet(self) + return self.wif +end + + +wifidev = utl.class() + +function wifidev.__init__(self, dev) + self.sid = dev + self.iwinfo = dev and sys.wifi.getiwinfo(dev) or { } +end + +function wifidev.get(self, opt) + return _get("wireless", self.sid, opt) +end + +function wifidev.set(self, opt, val) + return _set("wireless", self.sid, opt, val) +end + +function wifidev.name(self) + return self.sid +end + +function wifidev.hwmodes(self) + local l = self.iwinfo.hwmodelist + if l and next(l) then + return l + else + return { b = true, g = true } + end +end + +function wifidev.get_i18n(self) + local t = "Generic" + if self.iwinfo.type == "wl" then + t = "Broadcom" + elseif self.iwinfo.type == "madwifi" then + t = "Atheros" + end + + local m = "" + local l = self:hwmodes() + if l.a then m = m .. "a" end + if l.b then m = m .. "b" end + if l.g then m = m .. "g" end + if l.n then m = m .. "n" end + + return "%s 802.11%s Wireless Controller (%s)" %{ t, m, self:name() } +end + +function wifidev.is_up(self) + local up = false + + _uci_state:foreach("wireless", "wifi-iface", + function(s) + if s.device == self.sid then + if s.up == "1" then + up = true + return false + end + end + end) + + return up +end + +function wifidev.get_wifinet(self, net) + if _uci_real:get("wireless", net) == "wifi-iface" then + return wifinet(net) + else + local wnet = _wifi_lookup(net) + if wnet then + return wifinet(wnet) + end + end +end + +function wifidev.get_wifinets(self) + local nets = { } + + _uci_real:foreach("wireless", "wifi-iface", + function(s) + if s.device == self.sid then + nets[#nets+1] = wifinet(s['.name']) + end + end) + + return nets +end + +function wifidev.add_wifinet(self, options) + options = options or { } + options.device = self.sid + + local wnet = _uci_real:section("wireless", "wifi-iface", nil, options) + if wnet then + return wifinet(wnet, options) + end +end + +function wifidev.del_wifinet(self, net) + if utl.instanceof(net, wifinet) then + net = net.sid + elseif _uci_real:get("wireless", net) ~= "wifi-iface" then + net = _wifi_lookup(net) + end + + if net and _uci_real:get("wireless", net, "device") == self.sid then + _uci_real:delete("wireless", net) + return true + end + + return false +end + + +wifinet = utl.class() + +function wifinet.__init__(self, net, data) + self.sid = net + + local num = { } + local netid + _uci_real:foreach("wireless", "wifi-iface", + function(s) + if s.device then + num[s.device] = num[s.device] and num[s.device] + 1 or 1 + if s['.name'] == self.sid then + netid = "%s.network%d" %{ s.device, num[s.device] } + return false + end + end + end) + + local dev = _uci_state:get("wireless", self.sid, "ifname") or netid + + self.netid = netid + self.wdev = dev + self.iwinfo = dev and sys.wifi.getiwinfo(dev) or { } + self.iwdata = data or _uci_state:get_all("wireless", self.sid) or + _uci_real:get_all("wireless", self.sid) or { } +end + +function wifinet.get(self, opt) + return _get("wireless", self.sid, opt) +end + +function wifinet.set(self, opt, val) + return _set("wireless", self.sid, opt, val) +end + +function wifinet.set_list(self, opt, val) + if val and type(val) == "table" and #val > 0 then + return _uci_real:set_list("wireless", self.sid, opt, val) + else + return _uci_real:delete("wireless", self.sid, opt) + end +end + +function wifinet.mode(self) + return _uci_state:get("wireless", self.sid, "mode") or "ap" +end + +function wifinet.ssid(self) + return _uci_state:get("wireless", self.sid, "ssid") +end + +function wifinet.bssid(self) + return _uci_state:get("wireless", self.sid, "bssid") +end + +function wifinet.network(self) + return _uci_state:get("wifinet", self.sid, "network") +end + +function wifinet.id(self) + return self.netid +end + +function wifinet.name(self) + return self.sid +end + +function wifinet.ifname(self) + local ifname = self.iwinfo.ifname + if not ifname or ifname:match("^wifi%d") or ifname:match("^radio%d") then + ifname = self.wdev + end + return ifname +end + +function wifinet.get_device(self) + if self.iwdata.device then + return wifidev(self.iwdata.device) + end +end + +function wifinet.is_up(self) + return (self.iwdata.up == "1") +end + +function wifinet.active_mode(self) + local m = _stror(self.iwinfo.mode, self.iwdata.mode) or "ap" + + if m == "ap" then m = "Master" + elseif m == "sta" then m = "Client" + elseif m == "adhoc" then m = "Ad-Hoc" + elseif m == "mesh" then m = "Mesh" + elseif m == "monitor" then m = "Monitor" + end + + return m +end + +function wifinet.active_mode_i18n(self) + return lng.translate(self:active_mode()) +end + +function wifinet.active_ssid(self) + return _stror(self.iwinfo.ssid, self.iwdata.ssid) +end + +function wifinet.active_bssid(self) + return _stror(self.iwinfo.bssid, self.iwdata.bssid) or "00:00:00:00:00:00" +end + +function wifinet.active_encryption(self) + local enc = self.iwinfo and self.iwinfo.encryption + return enc and enc.description or "-" +end + +function wifinet.assoclist(self) + return self.iwinfo.assoclist or { } +end + +function wifinet.frequency(self) + local freq = self.iwinfo.frequency + if freq and freq > 0 then + return "%.03f" % (freq / 1000) + end +end + +function wifinet.bitrate(self) + local rate = self.iwinfo.bitrate + if rate and rate > 0 then + return (rate / 1000) + end +end + +function wifinet.channel(self) + return self.iwinfo.channel or + tostring(_uci_state:get("wireless", self.iwdata.device, "channel")) +end + +function wifinet.confchannel(self) + return tostring(_uci_state:get("wireless", self.iwdata.device, "channel")) +end + +function wifinet.bw(self) + return self.iwinfo.bw or + tostring(_uci_state:get("wireless", self.iwdata.device, "bw") or 0) +end + +function wifinet.confbw(self) + return tostring(_uci_state:get("wireless", self.iwdata.device, "bw") or 0) +end + +function wifinet.txpwr(self) + return self.iwinfo.txpwr or + tostring(_uci_state:get("wireless", self.iwdata.device, "txpwr")) +end + +function wifinet.signal(self) + return self.iwinfo.signal or 0 +end + +function wifinet.noise(self) + return self.iwinfo.noise or 0 +end + +function wifinet.country(self) + return self.iwinfo.country or "00" +end + +function wifinet.txpower(self) + local pwr = (self.iwinfo.txpower or 0) + return pwr + self:txpower_offset() +end + +function wifinet.txpower_offset(self) + return self.iwinfo.txpower_offset or 0 +end + +function wifinet.signal_level(self, s, n) + if self:active_bssid() ~= "00:00:00:00:00:00" then + local signal = s or self:signal() + local noise = n or self:noise() + + if signal < 0 and noise < 0 then + local snr = -1 * (noise - signal) + return math.floor(snr / 5) + else + return 0 + end + else + return -1 + end +end + +function wifinet.signal_percent(self) + local qc = self.iwinfo.quality or 0 + local qm = self.iwinfo.quality_max or 0 + + if qc > 0 and qm > 0 then + return math.floor((100 / qm) * qc) + else + return 0 + end +end + +function wifinet.shortname(self) + return "%s %q" %{ + lng.translate(self:active_mode()), + self:active_ssid() or self:active_bssid() + } +end + +function wifinet.get_i18n(self) + return "%s: %s %q (%s)" %{ + lng.translate("Wireless Network"), + lng.translate(self:active_mode()), + self:active_ssid() or self:active_bssid(), + self:ifname() + } +end + +function wifinet.adminlink(self) + return dsp.build_url("admin", "network", "wireless", self.netid) +end + +function wifinet.get_network(self) + return self:get_networks()[1] +end + +function wifinet.get_networks(self) + local nets = { } + local net + for net in utl.imatch(tostring(self.iwdata.network)) do + if _uci_real:get("network", net) == "interface" then + nets[#nets+1] = network(net) + end + end + table.sort(nets, function(a, b) return a.sid < b.sid end) + return nets +end + +function wifinet.get_interface(self) + return interface(self:ifname()) +end + + +-- setup base protocols +_M:register_protocol("static") +_M:register_protocol("dhcp") +_M:register_protocol("none") + +-- load protocol extensions +local exts = nfs.dir(utl.libpath() .. "/model/network") +if exts then + local ext + for ext in exts do + if ext:match("%.lua$") then + require("luci.model.network." .. ext:gsub("%.lua$", "")) + end + end +end diff --git a/Mi_Lua/luci/model/network/proto_ppp.lua b/Mi_Lua/luci/model/network/proto_ppp.lua new file mode 100644 index 0000000..dfd545f --- /dev/null +++ b/Mi_Lua/luci/model/network/proto_ppp.lua @@ -0,0 +1,104 @@ +--[[ +LuCI - Network model - 3G, PPP, PPtP, PPPoE and PPPoA protocol extension + +Copyright 2011 Jo-Philipp Wich + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +]]-- + +local netmod = luci.model.network + +local _, p +for _, p in ipairs({"ppp", "pptp", "pppoe", "pppoa", "3g", "l2tp"}) do + + local proto = netmod:register_protocol(p) + + function proto.get_i18n(self) + if p == "ppp" then + return luci.i18n.translate("PPP") + elseif p == "pptp" then + return luci.i18n.translate("PPtP") + elseif p == "3g" then + return luci.i18n.translate("UMTS/GPRS/EV-DO") + elseif p == "pppoe" then + return luci.i18n.translate("PPPoE") + elseif p == "pppoa" then + return luci.i18n.translate("PPPoATM") + elseif p == "l2tp" then + return luci.i18n.translate("L2TP") + end + end + + function proto.ifname(self) + return p .. "-" .. self.sid + end + + function proto.opkg_package(self) + if p == "ppp" then + return p + elseif p == "3g" then + return "comgt" + elseif p == "pptp" then + return "ppp-mod-pptp" + elseif p == "pppoe" then + return "ppp-mod-pppoe" + elseif p == "pppoa" then + return "ppp-mod-pppoa" + elseif p == "l2tp" then + return "xl2tpd" + end + end + + function proto.is_installed(self) + if p == "pppoa" then + return (nixio.fs.glob("/usr/lib/pppd/*/pppoatm.so")() ~= nil) + elseif p == "pppoe" then + return (nixio.fs.glob("/usr/lib/pppd/*/rp-pppoe.so")() ~= nil) + elseif p == "pptp" then + return (nixio.fs.glob("/usr/lib/pppd/*/pptp.so")() ~= nil) + elseif p == "3g" then + return nixio.fs.access("/lib/netifd/proto/3g.sh") + elseif p == "l2tp" then + return nixio.fs.access("/lib/netifd/proto/l2tp.sh") + else + return nixio.fs.access("/lib/netifd/proto/ppp.sh") + end + end + + function proto.is_floating(self) + return (p ~= "pppoe") + end + + function proto.is_virtual(self) + return true + end + + function proto.get_interfaces(self) + if self:is_floating() then + return nil + else + return netmod.protocol.get_interfaces(self) + end + end + + function proto.contains_interface(self, ifc) + if self:is_floating() then + return (netmod:ifnameof(ifc) == self:ifname()) + else + return netmod.protocol.contains_interface(self, ifc) + end + end + + netmod:register_pattern_virtual("^%s-%%w" % p) +end diff --git a/Mi_Lua/luci/model/uci.lua b/Mi_Lua/luci/model/uci.lua new file mode 100644 index 0000000..61ac344 --- /dev/null +++ b/Mi_Lua/luci/model/uci.lua @@ -0,0 +1,404 @@ +--[[ +LuCI - UCI model + +Description: +Generalized UCI model + +FileId: +$Id: uci.lua 8127 2011-12-20 19:02:14Z jow $ + +License: +Copyright 2008 Steven Barth + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +]]-- +local os = require "os" +local uci = require "uci" +local util = require "luci.util" +local table = require "table" + + +local setmetatable, rawget, rawset = setmetatable, rawget, rawset +local require, getmetatable = require, getmetatable +local error, pairs, ipairs = error, pairs, ipairs +local type, tostring, tonumber, unpack = type, tostring, tonumber, unpack + +--- LuCI UCI model library. +-- The typical workflow for UCI is: Get a cursor instance from the +-- cursor factory, modify data (via Cursor.add, Cursor.delete, etc.), +-- save the changes to the staging area via Cursor.save and finally +-- Cursor.commit the data to the actual config files. +-- LuCI then needs to Cursor.apply the changes so deamons etc. are +-- reloaded. +-- @cstyle instance +module "luci.model.uci" + +--- Create a new UCI-Cursor. +-- @class function +-- @name cursor +-- @return UCI-Cursor +cursor = uci.cursor + +APIVERSION = uci.APIVERSION + +--- Create a new Cursor initialized to the state directory. +-- @return UCI cursor +function cursor_state() + return cursor(nil, "/var/state") +end + + +inst = cursor() +inst_state = cursor_state() + +local Cursor = getmetatable(inst) + +--- Applies UCI configuration changes +-- @param configlist List of UCI configurations +-- @param command Don't apply only return the command +function Cursor.apply(self, configlist, command) + configlist = self:_affected(configlist) + if command then + return { "/sbin/luci-reload", unpack(configlist) } + else + return os.execute("/sbin/luci-reload %s >/dev/null 2>&1" + % table.concat(configlist, " ")) + end +end + + +--- Delete all sections of a given type that match certain criteria. +-- @param config UCI config +-- @param type UCI section type +-- @param comparator Function that will be called for each section and +-- returns a boolean whether to delete the current section (optional) +function Cursor.delete_all(self, config, stype, comparator) + local del = {} + + if type(comparator) == "table" then + local tbl = comparator + comparator = function(section) + for k, v in pairs(tbl) do + if section[k] ~= v then + return false + end + end + return true + end + end + + local function helper (section) + + if not comparator or comparator(section) then + del[#del+1] = section[".name"] + end + end + + self:foreach(config, stype, helper) + + for i, j in ipairs(del) do + self:delete(config, j) + end +end + +--- Create a new section and initialize it with data. +-- @param config UCI config +-- @param type UCI section type +-- @param name UCI section name (optional) +-- @param values Table of key - value pairs to initialize the section with +-- @return Name of created section +function Cursor.section(self, config, type, name, values) + local stat = true + if name then + stat = self:set(config, name, type) + else + name = self:add(config, type) + stat = name and true + end + + if stat and values then + stat = self:tset(config, name, values) + end + + return stat and name +end + +--- Updated the data of a section using data from a table. +-- @param config UCI config +-- @param section UCI section name (optional) +-- @param values Table of key - value pairs to update the section with +function Cursor.tset(self, config, section, values) + local stat = true + for k, v in pairs(values) do + if k:sub(1, 1) ~= "." then + stat = stat and self:set(config, section, k, v) + end + end + return stat +end + +--- Get a boolean option and return it's value as true or false. +-- @param config UCI config +-- @param section UCI section name +-- @param option UCI option +-- @return Boolean +function Cursor.get_bool(self, ...) + local val = self:get(...) + return ( val == "1" or val == "true" or val == "yes" or val == "on" ) +end + +--- Get an option or list and return values as table. +-- @param config UCI config +-- @param section UCI section name +-- @param option UCI option +-- @return UCI value +function Cursor.get_list(self, config, section, option) + if config and section and option then + local val = self:get(config, section, option) + return ( type(val) == "table" and val or { val } ) + end + return nil +end + +--- Get the given option from the first section with the given type. +-- @param config UCI config +-- @param type UCI section type +-- @param option UCI option (optional) +-- @param default Default value (optional) +-- @return UCI value +function Cursor.get_first(self, conf, stype, opt, def) + local rv = def + + self:foreach(conf, stype, + function(s) + local val = not opt and s['.name'] or s[opt] + + if type(def) == "number" then + val = tonumber(val) + elseif type(def) == "boolean" then + val = (val == "1" or val == "true" or + val == "yes" or val == "on") + end + + if val ~= nil then + rv = val + return false + end + end) + + return rv +end + +--- Set given values as list. +-- @param config UCI config +-- @param section UCI section name +-- @param option UCI option +-- @param value UCI value +-- @return Boolean whether operation succeeded +function Cursor.set_list(self, config, section, option, value) + if config and section and option then + return self:set( + config, section, option, + ( type(value) == "table" and value or { value } ) + ) + end + return false +end + +-- Return a list of initscripts affected by configuration changes. +function Cursor._affected(self, configlist) + configlist = type(configlist) == "table" and configlist or {configlist} + + local c = cursor() + c:load("ucitrack") + + -- Resolve dependencies + local reloadlist = {} + + local function _resolve_deps(name) + local reload = {name} + local deps = {} + + c:foreach("ucitrack", name, + function(section) + if section.affects then + for i, aff in ipairs(section.affects) do + deps[#deps+1] = aff + end + end + end) + + for i, dep in ipairs(deps) do + for j, add in ipairs(_resolve_deps(dep)) do + reload[#reload+1] = add + end + end + + return reload + end + + -- Collect initscripts + for j, config in ipairs(configlist) do + for i, e in ipairs(_resolve_deps(config)) do + if not util.contains(reloadlist, e) then + reloadlist[#reloadlist+1] = e + end + end + end + + return reloadlist +end + +--- Create a sub-state of this cursor. The sub-state is tied to the parent +-- curser, means it the parent unloads or loads configs, the sub state will +-- do so as well. +-- @return UCI state cursor tied to the parent cursor +function Cursor.substate(self) + Cursor._substates = Cursor._substates or { } + Cursor._substates[self] = Cursor._substates[self] or cursor_state() + return Cursor._substates[self] +end + +local _load = Cursor.load +function Cursor.load(self, ...) + if Cursor._substates and Cursor._substates[self] then + _load(Cursor._substates[self], ...) + end + return _load(self, ...) +end + +local _unload = Cursor.unload +function Cursor.unload(self, ...) + if Cursor._substates and Cursor._substates[self] then + _unload(Cursor._substates[self], ...) + end + return _unload(self, ...) +end + + +--- Add an anonymous section. +-- @class function +-- @name Cursor.add +-- @param config UCI config +-- @param type UCI section type +-- @return Name of created section + +--- Get a table of saved but uncommitted changes. +-- @class function +-- @name Cursor.changes +-- @param config UCI config +-- @return Table of changes +-- @see Cursor.save + +--- Commit saved changes. +-- @class function +-- @name Cursor.commit +-- @param config UCI config +-- @return Boolean whether operation succeeded +-- @see Cursor.revert +-- @see Cursor.save + +--- Deletes a section or an option. +-- @class function +-- @name Cursor.delete +-- @param config UCI config +-- @param section UCI section name +-- @param option UCI option (optional) +-- @return Boolean whether operation succeeded + +--- Call a function for every section of a certain type. +-- @class function +-- @name Cursor.foreach +-- @param config UCI config +-- @param type UCI section type +-- @param callback Function to be called +-- @return Boolean whether operation succeeded + +--- Get a section type or an option +-- @class function +-- @name Cursor.get +-- @param config UCI config +-- @param section UCI section name +-- @param option UCI option (optional) +-- @return UCI value + +--- Get all sections of a config or all values of a section. +-- @class function +-- @name Cursor.get_all +-- @param config UCI config +-- @param section UCI section name (optional) +-- @return Table of UCI sections or table of UCI values + +--- Manually load a config. +-- @class function +-- @name Cursor.load +-- @param config UCI config +-- @return Boolean whether operation succeeded +-- @see Cursor.save +-- @see Cursor.unload + +--- Revert saved but uncommitted changes. +-- @class function +-- @name Cursor.revert +-- @param config UCI config +-- @return Boolean whether operation succeeded +-- @see Cursor.commit +-- @see Cursor.save + +--- Saves changes made to a config to make them committable. +-- @class function +-- @name Cursor.save +-- @param config UCI config +-- @return Boolean whether operation succeeded +-- @see Cursor.load +-- @see Cursor.unload + +--- Set a value or create a named section. +-- @class function +-- @name Cursor.set +-- @param config UCI config +-- @param section UCI section name +-- @param option UCI option or UCI section type +-- @param value UCI value or nil if you want to create a section +-- @return Boolean whether operation succeeded + +--- Get the configuration directory. +-- @class function +-- @name Cursor.get_confdir +-- @return Configuration directory + +--- Get the directory for uncomitted changes. +-- @class function +-- @name Cursor.get_savedir +-- @return Save directory + +--- Set the configuration directory. +-- @class function +-- @name Cursor.set_confdir +-- @param directory UCI configuration directory +-- @return Boolean whether operation succeeded + +--- Set the directory for uncommited changes. +-- @class function +-- @name Cursor.set_savedir +-- @param directory UCI changes directory +-- @return Boolean whether operation succeeded + +--- Discard changes made to a config. +-- @class function +-- @name Cursor.unload +-- @param config UCI config +-- @return Boolean whether operation succeeded +-- @see Cursor.load +-- @see Cursor.save diff --git a/Mi_Lua/luci/sauth.lua b/Mi_Lua/luci/sauth.lua new file mode 100644 index 0000000..2aa8c2c --- /dev/null +++ b/Mi_Lua/luci/sauth.lua @@ -0,0 +1,165 @@ +--[[ + +Session authentication +(c) 2008 Steven Barth + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +$Id: sauth.lua 8900 2012-08-08 09:48:47Z jow $ + +]]-- + +--- LuCI session library. +module("luci.sauth", package.seeall) +require("luci.util") +require("luci.sys") +require("luci.config") +local nixio = require "nixio", require "nixio.util" +local fs = require "nixio.fs" + +local XQLog = require("xiaoqiang.XQLog") + +luci.config.sauth = luci.config.sauth or {} +sessionpath = luci.config.sauth.sessionpath +sessiontime = tonumber(luci.config.sauth.sessiontime) or 15 * 60 + +--- Prepare session storage by creating the session directory. +function prepare() + fs.mkdir(sessionpath, 700) + if not sane() then + error("Security Exception: Session path is not sane!") + end +end + +local function _read(id) + local blob = fs.readfile(sessionpath .. "/" .. id) + return blob +end + +local function _write(id, data) + local tempid = luci.sys.uniqueid(16) + local tempfile = sessionpath .. "/" .. tempid + local sessfile = sessionpath .. "/" .. id + local f = nixio.open(tempfile, "w", 600) + f:writeall(data) + f:close() + fs.rename(tempfile, sessfile) +end + +local function _checkid(id) + return not not (id and #id == 32 and id:match("^[a-fA-F0-9]+$")) +end + +--- Write session data to a session file. +-- @param id Session identifier +-- @param data Session data table +function write(id, data) + if not sane() then + prepare() + end + + if not _checkid(id) then + XQLog.log(3,"Security Exception: Session ID is invalid! sauth.write") + return + end + + if type(data) ~= "table" then + XQLog.log(3,"Security Exception: Session data invalid! sauth.write") + return + end + + data.atime = luci.sys.uptime() + + _write(id, luci.util.get_bytecode(data)) +end + +--- Read a session and return its content. +-- @param id Session identifier +-- @return Session data table or nil if the given id is not found +function read(id) + if not id or #id == 0 then + return nil + end + + if not _checkid(id) then + XQLog.log(3,"Security Exception: Session ID is invalid! sauth.read") + return nil + end + + if not sane(sessionpath .. "/" .. id) then + return nil + end + + local blob = _read(id) + local func = loadstring(blob) + setfenv(func, {}) + + local sess = func() + if type(sess) ~= "table" then + XQLog.log(3,"Security Exception: Session data invalid! sauth.read") + return nil + end + + if sess.atime and sess.atime + sessiontime < luci.sys.uptime() then + kill(id) + return nil + end + + -- refresh atime in session + write(id, sess) + + return sess +end + +--- Check whether Session environment is sane. +-- @return Boolean status +function sane(file) + return luci.sys.process.info("uid") + == fs.stat(file or sessionpath, "uid") + and fs.stat(file or sessionpath, "modestr") + == (file and "rw-------" or "rwx------") +end + +--- Kills a session +-- @param id Session identifier +function kill(id) + if not _checkid(id) then + XQLog.log(3,"Security Exception: Session ID is invalid! sauth.kill") + else + fs.unlink(sessionpath .. "/" .. id) + end +end + +--- Remove all expired session data files +function reap() + if sane() then + local id + for id in nixio.fs.dir(sessionpath) do + if _checkid(id) then + -- reading the session will kill it if it is expired + read(id) + end + end + end +end + +--- Get available session data +function available() + if sane() then + local id + for id in nixio.fs.dir(sessionpath) do + if _checkid(id) then + -- reading the session will kill it if it is expired + local available = read(id) + if available then + return available + end + end + end + end + return nil +end \ No newline at end of file diff --git a/Mi_Lua/luci/sgi/cgi.lua b/Mi_Lua/luci/sgi/cgi.lua new file mode 100644 index 0000000..babf650 --- /dev/null +++ b/Mi_Lua/luci/sgi/cgi.lua @@ -0,0 +1,95 @@ +--[[ +LuCI - SGI-Module for CGI + +Description: +Server Gateway Interface for CGI + +FileId: +$Id: cgi.lua 6535 2010-11-23 01:02:21Z soma $ + +License: +Copyright 2008 Steven Barth + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +]]-- +exectime = os.clock() +module("luci.sgi.cgi", package.seeall) +local ltn12 = require("luci.ltn12") +require("nixio.util") +require("luci.http") +require("luci.sys") +require("luci.dispatcher") + +-- Limited source to avoid endless blocking +local function limitsource(handle, limit) + limit = limit or 0 + local BLOCKSIZE = ltn12.BLOCKSIZE + + return function() + if limit < 1 then + handle:close() + return nil + else + local read = (limit > BLOCKSIZE) and BLOCKSIZE or limit + limit = limit - read + + local chunk = handle:read(read) + if not chunk then handle:close() end + return chunk + end + end +end + +function run() + local r = luci.http.Request( + luci.sys.getenv(), + limitsource(io.stdin, tonumber(luci.sys.getenv("CONTENT_LENGTH"))), + ltn12.sink.file(io.stderr) + ) + + local x = coroutine.create(luci.dispatcher.httpdispatch) + local hcache = "" + local active = true + + while coroutine.status(x) ~= "dead" do + local res, id, data1, data2 = coroutine.resume(x, r) + + if not res then + print("Status: 500 Internal Server Error") + print("Content-Type: text/plain\n") + print(id) + break; + end + + if active then + if id == 1 then + io.write("Status: " .. tostring(data1) .. " " .. data2 .. "\r\n") + elseif id == 2 then + hcache = hcache .. data1 .. ": " .. data2 .. "\r\n" + elseif id == 3 then + io.write(hcache) + io.write("\r\n") + elseif id == 4 then + io.write(tostring(data1 or "")) + elseif id == 5 then + io.flush() + io.close() + active = false + elseif id == 6 then + data1:copyz(nixio.stdout, data2) + data1:close() + end + end + end +end diff --git a/Mi_Lua/luci/sgi/uhttpd.lua b/Mi_Lua/luci/sgi/uhttpd.lua new file mode 100644 index 0000000..5915b69 --- /dev/null +++ b/Mi_Lua/luci/sgi/uhttpd.lua @@ -0,0 +1,121 @@ +--[[ +LuCI - Server Gateway Interface for the uHTTPd server + +Copyright 2010 Jo-Philipp Wich + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +]]-- + +require "nixio.util" +require "luci.http" +require "luci.sys" +require "luci.dispatcher" +require "luci.ltn12" + +function handle_request(env) + exectime = os.clock() + local renv = { + CONTENT_LENGTH = env.CONTENT_LENGTH, + CONTENT_TYPE = env.CONTENT_TYPE, + REQUEST_METHOD = env.REQUEST_METHOD, + REQUEST_URI = env.REQUEST_URI, + PATH_INFO = env.PATH_INFO, + SCRIPT_NAME = env.SCRIPT_NAME:gsub("/+$", ""), + SCRIPT_FILENAME = env.SCRIPT_NAME, + SERVER_PROTOCOL = env.SERVER_PROTOCOL, + QUERY_STRING = env.QUERY_STRING + } + + local k, v + for k, v in pairs(env.headers) do + k = k:upper():gsub("%-", "_") + renv["HTTP_" .. k] = v + end + + local len = env.CONTENT_LENGTH or 0 + local function recv() + if len > 0 then + local rlen, rbuf = uhttpd.recv(4096) + if rlen >= 0 then + len = len - rlen + return rbuf + end + end + return nil + end + + local function send(...) + return uhttpd.send(...) + end + + local function sendc(...) + if env.HTTP_VERSION > 1.0 then + return uhttpd.sendc(...) + else + return uhttpd.send(...) + end + end + + local req = luci.http.Request( + renv, recv, luci.ltn12.sink.file(io.stderr) + ) + + + local x = coroutine.create(luci.dispatcher.httpdispatch) + local hcache = { } + local active = true + + if env.HTTP_VERSION > 1.0 then + hcache["Transfer-Encoding"] = "chunked" + end + + while coroutine.status(x) ~= "dead" do + local res, id, data1, data2 = coroutine.resume(x, req) + + if not res then + send(env.SERVER_PROTOCOL) + send(" 500 Internal Server Error\r\n") + send("Content-Type: text/plain\r\n\r\n") + send(tostring(id)) + break + end + + if active then + if id == 1 then + send(env.SERVER_PROTOCOL) + send(" ") + send(tostring(data1)) + send(" ") + send(tostring(data2)) + send("\r\n") + elseif id == 2 then + hcache[data1] = data2 + elseif id == 3 then + for k, v in pairs(hcache) do + send(tostring(k)) + send(": ") + send(tostring(v)) + send("\r\n") + end + send("\r\n") + elseif id == 4 then + sendc(tostring(data1 or "")) + elseif id == 5 then + active = false + elseif id == 6 then + data1:copyz(nixio.stdout, data2) + end + end + end +end diff --git a/Mi_Lua/luci/store.lua b/Mi_Lua/luci/store.lua new file mode 100644 index 0000000..c33ef07 --- /dev/null +++ b/Mi_Lua/luci/store.lua @@ -0,0 +1,16 @@ +--[[ + +LuCI - Lua Development Framework +(c) 2009 Steven Barth +(c) 2009 Jo-Philipp Wich + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +]]-- + +local util = require "luci.util" +module("luci.store", util.threadlocal) \ No newline at end of file diff --git a/Mi_Lua/luci/sys.lua b/Mi_Lua/luci/sys.lua new file mode 100644 index 0000000..3867214 --- /dev/null +++ b/Mi_Lua/luci/sys.lua @@ -0,0 +1,966 @@ +--[[ +LuCI - System library + +Description: +Utilities for interaction with the Linux system + +FileId: +$Id: sys.lua 9623 2013-01-18 14:08:37Z jow $ + +License: +Copyright 2008 Steven Barth + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +]]-- + + +local io = require "io" +local os = require "os" +local table = require "table" +local nixio = require "nixio" +local fs = require "nixio.fs" +local uci = require "luci.model.uci" + +local luci = {} +luci.util = require "luci.util" +luci.ip = require "luci.ip" + +local tonumber, ipairs, pairs, pcall, type, next, setmetatable, require, select = + tonumber, ipairs, pairs, pcall, type, next, setmetatable, require, select + + +--- LuCI Linux and POSIX system utilities. +module "luci.sys" + +--- Execute a given shell command and return the error code +-- @class function +-- @name call +-- @param ... Command to call +-- @return Error code of the command +function call(...) + return os.execute(...) / 256 +end + +--- Execute a given shell command and capture its standard output +-- @class function +-- @name exec +-- @param command Command to call +-- @return String containg the return the output of the command +exec = luci.util.exec + +--- Retrieve information about currently mounted file systems. +-- @return Table containing mount information +function mounts() + local data = {} + local k = {"fs", "blocks", "used", "available", "percent", "mountpoint"} + local ps = luci.util.execi("df") + + if not ps then + return + else + ps() + end + + for line in ps do + local row = {} + + local j = 1 + for value in line:gmatch("[^%s]+") do + row[k[j]] = value + j = j + 1 + end + + if row[k[1]] then + + -- this is a rather ugly workaround to cope with wrapped lines in + -- the df output: + -- + -- /dev/scsi/host0/bus0/target0/lun0/part3 + -- 114382024 93566472 15005244 86% /mnt/usb + -- + + if not row[k[2]] then + j = 2 + line = ps() + for value in line:gmatch("[^%s]+") do + row[k[j]] = value + j = j + 1 + end + end + + table.insert(data, row) + end + end + + return data +end + +--- Retrieve environment variables. If no variable is given then a table +-- containing the whole environment is returned otherwise this function returns +-- the corresponding string value for the given name or nil if no such variable +-- exists. +-- @class function +-- @name getenv +-- @param var Name of the environment variable to retrieve (optional) +-- @return String containg the value of the specified variable +-- @return Table containing all variables if no variable name is given +getenv = nixio.getenv + +--- Get or set the current hostname. +-- @param String containing a new hostname to set (optional) +-- @return String containing the system hostname +function hostname(newname) + if type(newname) == "string" and #newname > 0 then + fs.writefile( "/proc/sys/kernel/hostname", newname ) + return newname + else + return nixio.uname().nodename + end +end + +--- Returns the contents of a documented referred by an URL. +-- @param url The URL to retrieve +-- @param stream Return a stream instead of a buffer +-- @param target Directly write to target file name +-- @return String containing the contents of given the URL +function httpget(url, stream, target) + if not target then + local source = stream and io.popen or luci.util.exec + return source("wget -qO- '"..url:gsub("'", "").."'") + else + return os.execute("wget -qO '%s' '%s'" % + {target:gsub("'", ""), url:gsub("'", "")}) + end +end + +--- Returns the system load average values. +-- @return String containing the average load value 1 minute ago +-- @return String containing the average load value 5 minutes ago +-- @return String containing the average load value 15 minutes ago +function loadavg() + local info = nixio.sysinfo() + return info.loads[1], info.loads[2], info.loads[3] +end + +--- Initiate a system reboot. +-- @return Return value of os.execute() +function reboot() + return os.execute("reboot >/dev/null 2>&1") +end + +--- Returns the system type, cpu name and installed physical memory. +-- @return String containing the system or platform identifier +-- @return String containing hardware model information +-- @return String containing the total memory amount in kB +-- @return String containing the memory used for caching in kB +-- @return String containing the memory used for buffering in kB +-- @return String containing the free memory amount in kB +-- @return String containing the cpu bogomips (number) +function sysinfo() + local cpuinfo = fs.readfile("/proc/cpuinfo") + local meminfo = fs.readfile("/proc/meminfo") + + local memtotal = tonumber(meminfo:match("MemTotal:%s*(%d+)")) + local memcached = tonumber(meminfo:match("\nCached:%s*(%d+)")) + local memfree = tonumber(meminfo:match("MemFree:%s*(%d+)")) + local membuffers = tonumber(meminfo:match("Buffers:%s*(%d+)")) + local bogomips = tonumber(cpuinfo:match("[Bb]ogo[Mm][Ii][Pp][Ss].-: ([^\n]+)")) or 0 + + local system = + cpuinfo:match("system type\t+: ([^\n]+)") or + cpuinfo:match("Processor\t+: ([^\n]+)") or + cpuinfo:match("model name\t+: ([^\n]+)") + + local model = + luci.util.pcdata(fs.readfile("/tmp/sysinfo/model")) or + cpuinfo:match("machine\t+: ([^\n]+)") or + cpuinfo:match("Hardware\t+: ([^\n]+)") or + luci.util.pcdata(fs.readfile("/proc/diag/model")) or + nixio.uname().machine or + system + + return system, model, memtotal, memcached, membuffers, memfree, bogomips +end + +--- Retrieves the output of the "logread" command. +-- @return String containing the current log buffer +function syslog() + return luci.util.exec("logread") +end + +--- Retrieves the output of the "dmesg" command. +-- @return String containing the current log buffer +function dmesg() + return luci.util.exec("dmesg") +end + +--- Generates a random id with specified length. +-- @param bytes Number of bytes for the unique id +-- @return String containing hex encoded id +function uniqueid(bytes) + local rand = fs.readfile("/dev/urandom", bytes) + return rand and nixio.bin.hexlify(rand) +end + +--- Returns the current system uptime stats. +-- @return String containing total uptime in seconds +function uptime() + return nixio.sysinfo().uptime +end + + +--- LuCI system utilities / network related functions. +-- @class module +-- @name luci.sys.net +net = {} + +--- Returns the current arp-table entries as two-dimensional table. +-- @return Table of table containing the current arp entries. +-- The following fields are defined for arp entry objects: +-- { "IP address", "HW address", "HW type", "Flags", "Mask", "Device" } +function net.arptable(callback) + local arp, e, r, v + if fs.access("/proc/net/arp") then + for e in io.lines("/proc/net/arp") do + local r = { }, v + for v in e:gmatch("%S+") do + r[#r+1] = v + end + + if r[1] ~= "IP" then + local x = { + ["IP address"] = r[1], + ["HW type"] = r[2], + ["Flags"] = r[3], + ["HW address"] = r[4], + ["Mask"] = r[5], + ["Device"] = r[6] + } + + if callback then + callback(x) + else + arp = arp or { } + arp[#arp+1] = x + end + end + end + end + return arp +end + +local function _nethints(what, callback) + local _, k, e, mac, ip, name + local cur = uci.cursor() + local ifn = { } + local hosts = { } + + local function _add(i, ...) + local k = select(i, ...) + if k then + if not hosts[k] then hosts[k] = { } end + hosts[k][1] = select(1, ...) or hosts[k][1] + hosts[k][2] = select(2, ...) or hosts[k][2] + hosts[k][3] = select(3, ...) or hosts[k][3] + hosts[k][4] = select(4, ...) or hosts[k][4] + end + end + + if fs.access("/proc/net/arp") then + for e in io.lines("/proc/net/arp") do + ip, mac = e:match("^([%d%.]+)%s+%S+%s+%S+%s+([a-fA-F0-9:]+)%s+") + if ip and mac then + _add(what, mac:upper(), ip, nil, nil) + end + end + end + + if fs.access("/etc/ethers") then + for e in io.lines("/etc/ethers") do + mac, ip = e:match("^([a-f0-9]%S+) (%S+)") + if mac and ip then + _add(what, mac:upper(), ip, nil, nil) + end + end + end + + if fs.access("/var/dhcp.leases") then + for e in io.lines("/var/dhcp.leases") do + mac, ip, name = e:match("^%d+ (%S+) (%S+) (%S+)") + if mac and ip then + _add(what, mac:upper(), ip, nil, name ~= "*" and name) + end + end + end + + cur:foreach("dhcp", "host", + function(s) + for mac in luci.util.imatch(s.mac) do + _add(what, mac:upper(), s.ip, nil, s.name) + end + end) + + for _, e in ipairs(nixio.getifaddrs()) do + if e.name ~= "lo" then + ifn[e.name] = ifn[e.name] or { } + if e.family == "packet" and e.addr and #e.addr == 17 then + ifn[e.name][1] = e.addr:upper() + elseif e.family == "inet" then + ifn[e.name][2] = e.addr + elseif e.family == "inet6" then + ifn[e.name][3] = e.addr + end + end + end + + for _, e in pairs(ifn) do + if e[what] and (e[2] or e[3]) then + _add(what, e[1], e[2], e[3], e[4]) + end + end + + for _, e in luci.util.kspairs(hosts) do + callback(e[1], e[2], e[3], e[4]) + end +end + +--- Returns a two-dimensional table of mac address hints. +-- @return Table of table containing known hosts from various sources. +-- Each entry contains the values in the following order: +-- [ "mac", "name" ] +function net.mac_hints(callback) + if callback then + _nethints(1, function(mac, v4, v6, name) + name = name or nixio.getnameinfo(v4 or v6, nil, 100) or v4 + if name and name ~= mac then + callback(mac, name or nixio.getnameinfo(v4 or v6, nil, 100) or v4) + end + end) + else + local rv = { } + _nethints(1, function(mac, v4, v6, name) + name = name or nixio.getnameinfo(v4 or v6, nil, 100) or v4 + if name and name ~= mac then + rv[#rv+1] = { mac, name or nixio.getnameinfo(v4 or v6, nil, 100) or v4 } + end + end) + return rv + end +end + +--- Returns a two-dimensional table of IPv4 address hints. +-- @return Table of table containing known hosts from various sources. +-- Each entry contains the values in the following order: +-- [ "ip", "name" ] +function net.ipv4_hints(callback) + if callback then + _nethints(2, function(mac, v4, v6, name) + name = name or nixio.getnameinfo(v4, nil, 100) or mac + if name and name ~= v4 then + callback(v4, name) + end + end) + else + local rv = { } + _nethints(2, function(mac, v4, v6, name) + name = name or nixio.getnameinfo(v4, nil, 100) or mac + if name and name ~= v4 then + rv[#rv+1] = { v4, name } + end + end) + return rv + end +end + +--- Returns a two-dimensional table of IPv6 address hints. +-- @return Table of table containing known hosts from various sources. +-- Each entry contains the values in the following order: +-- [ "ip", "name" ] +function net.ipv6_hints(callback) + if callback then + _nethints(3, function(mac, v4, v6, name) + name = name or nixio.getnameinfo(v6, nil, 100) or mac + if name and name ~= v6 then + callback(v6, name) + end + end) + else + local rv = { } + _nethints(3, function(mac, v4, v6, name) + name = name or nixio.getnameinfo(v6, nil, 100) or mac + if name and name ~= v6 then + rv[#rv+1] = { v6, name } + end + end) + return rv + end +end + +--- Returns conntrack information +-- @return Table with the currently tracked IP connections +function net.conntrack(callback) + local connt = {} + if fs.access("/proc/net/nf_conntrack", "r") then + for line in io.lines("/proc/net/nf_conntrack") do + line = line:match "^(.-( [^ =]+=).-)%2" + local entry, flags = _parse_mixed_record(line, " +") + if flags[6] ~= "TIME_WAIT" then + entry.layer3 = flags[1] + entry.layer4 = flags[3] + for i=1, #entry do + entry[i] = nil + end + + if callback then + callback(entry) + else + connt[#connt+1] = entry + end + end + end + elseif fs.access("/proc/net/ip_conntrack", "r") then + for line in io.lines("/proc/net/ip_conntrack") do + line = line:match "^(.-( [^ =]+=).-)%2" + local entry, flags = _parse_mixed_record(line, " +") + if flags[4] ~= "TIME_WAIT" then + entry.layer3 = "ipv4" + entry.layer4 = flags[1] + for i=1, #entry do + entry[i] = nil + end + + if callback then + callback(entry) + else + connt[#connt+1] = entry + end + end + end + else + return nil + end + return connt +end + +--- Determine the current IPv4 default route. If multiple default routes exist, +-- return the one with the lowest metric. +-- @return Table with the properties of the current default route. +-- The following fields are defined: +-- { "dest", "gateway", "metric", "refcount", "usecount", "irtt", +-- "flags", "device" } +function net.defaultroute() + local route + + net.routes(function(rt) + if rt.dest:prefix() == 0 and (not route or route.metric > rt.metric) then + route = rt + end + end) + + return route +end + +--- Determine the current IPv6 default route. If multiple default routes exist, +-- return the one with the lowest metric. +-- @return Table with the properties of the current default route. +-- The following fields are defined: +-- { "source", "dest", "nexthop", "metric", "refcount", "usecount", +-- "flags", "device" } +function net.defaultroute6() + local route + + net.routes6(function(rt) + if rt.dest:prefix() == 0 and rt.device ~= "lo" and + (not route or route.metric > rt.metric) + then + route = rt + end + end) + + if not route then + local global_unicast = luci.ip.IPv6("2000::/3") + net.routes6(function(rt) + if rt.dest:equal(global_unicast) and + (not route or route.metric > rt.metric) + then + route = rt + end + end) + end + + return route +end + +--- Determine the names of available network interfaces. +-- @return Table containing all current interface names +function net.devices() + local devs = {} + for k, v in ipairs(nixio.getifaddrs()) do + if v.family == "packet" then + devs[#devs+1] = v.name + end + end + return devs +end + + +--- Return information about available network interfaces. +-- @return Table containing all current interface names and their information +function net.deviceinfo() + local devs = {} + for k, v in ipairs(nixio.getifaddrs()) do + if v.family == "packet" then + local d = v.data + d[1] = d.rx_bytes + d[2] = d.rx_packets + d[3] = d.rx_errors + d[4] = d.rx_dropped + d[5] = 0 + d[6] = 0 + d[7] = 0 + d[8] = d.multicast + d[9] = d.tx_bytes + d[10] = d.tx_packets + d[11] = d.tx_errors + d[12] = d.tx_dropped + d[13] = 0 + d[14] = d.collisions + d[15] = 0 + d[16] = 0 + devs[v.name] = d + end + end + return devs +end + + +-- Determine the MAC address belonging to the given IP address. +-- @param ip IPv4 address +-- @return String containing the MAC address or nil if it cannot be found +function net.ip4mac(ip) + local mac = nil + net.arptable(function(e) + if e["IP address"] == ip then + mac = e["HW address"] + end + end) + return mac +end + +--- Returns the current kernel routing table entries. +-- @return Table of tables with properties of the corresponding routes. +-- The following fields are defined for route entry tables: +-- { "dest", "gateway", "metric", "refcount", "usecount", "irtt", +-- "flags", "device" } +function net.routes(callback) + local routes = { } + + for line in io.lines("/proc/net/route") do + + local dev, dst_ip, gateway, flags, refcnt, usecnt, metric, + dst_mask, mtu, win, irtt = line:match( + "([^%s]+)\t([A-F0-9]+)\t([A-F0-9]+)\t([A-F0-9]+)\t" .. + "(%d+)\t(%d+)\t(%d+)\t([A-F0-9]+)\t(%d+)\t(%d+)\t(%d+)" + ) + + if dev then + gateway = luci.ip.Hex( gateway, 32, luci.ip.FAMILY_INET4 ) + dst_mask = luci.ip.Hex( dst_mask, 32, luci.ip.FAMILY_INET4 ) + dst_ip = luci.ip.Hex( + dst_ip, dst_mask:prefix(dst_mask), luci.ip.FAMILY_INET4 + ) + + local rt = { + dest = dst_ip, + gateway = gateway, + metric = tonumber(metric), + refcount = tonumber(refcnt), + usecount = tonumber(usecnt), + mtu = tonumber(mtu), + window = tonumber(window), + irtt = tonumber(irtt), + flags = tonumber(flags, 16), + device = dev + } + + if callback then + callback(rt) + else + routes[#routes+1] = rt + end + end + end + + return routes +end + +--- Returns the current ipv6 kernel routing table entries. +-- @return Table of tables with properties of the corresponding routes. +-- The following fields are defined for route entry tables: +-- { "source", "dest", "nexthop", "metric", "refcount", "usecount", +-- "flags", "device" } +function net.routes6(callback) + if fs.access("/proc/net/ipv6_route", "r") then + local routes = { } + + for line in io.lines("/proc/net/ipv6_route") do + + local dst_ip, dst_prefix, src_ip, src_prefix, nexthop, + metric, refcnt, usecnt, flags, dev = line:match( + "([a-f0-9]+) ([a-f0-9]+) " .. + "([a-f0-9]+) ([a-f0-9]+) " .. + "([a-f0-9]+) ([a-f0-9]+) " .. + "([a-f0-9]+) ([a-f0-9]+) " .. + "([a-f0-9]+) +([^%s]+)" + ) + + if dst_ip and dst_prefix and + src_ip and src_prefix and + nexthop and metric and + refcnt and usecnt and + flags and dev + then + src_ip = luci.ip.Hex( + src_ip, tonumber(src_prefix, 16), luci.ip.FAMILY_INET6, false + ) + + dst_ip = luci.ip.Hex( + dst_ip, tonumber(dst_prefix, 16), luci.ip.FAMILY_INET6, false + ) + + nexthop = luci.ip.Hex( nexthop, 128, luci.ip.FAMILY_INET6, false ) + + local rt = { + source = src_ip, + dest = dst_ip, + nexthop = nexthop, + metric = tonumber(metric, 16), + refcount = tonumber(refcnt, 16), + usecount = tonumber(usecnt, 16), + flags = tonumber(flags, 16), + device = dev, + + -- lua number is too small for storing the metric + -- add a metric_raw field with the original content + metric_raw = metric + } + + if callback then + callback(rt) + else + routes[#routes+1] = rt + end + end + end + + return routes + end +end + +--- Tests whether the given host responds to ping probes. +-- @param host String containing a hostname or IPv4 address +-- @return Number containing 0 on success and >= 1 on error +function net.pingtest(host) + return os.execute("ping -c1 '"..host:gsub("'", '').."' >/dev/null 2>&1") +end + + +--- LuCI system utilities / process related functions. +-- @class module +-- @name luci.sys.process +process = {} + +--- Get the current process id. +-- @class function +-- @name process.info +-- @return Number containing the current pid +function process.info(key) + local s = {uid = nixio.getuid(), gid = nixio.getgid()} + return not key and s or s[key] +end + +--- Retrieve information about currently running processes. +-- @return Table containing process information +function process.list() + local data = {} + local k + local ps = luci.util.execi("/bin/busybox top -bn1") + + if not ps then + return + end + + for line in ps do + local pid, ppid, user, stat, vsz, mem, cpu, cmd = line:match( + "^ *(%d+) +(%d+) +(%S.-%S) +([RSDZTW][W ][= 1 on error +function user.setpasswd(username, password) + if username and username:lower()=="admin" then + username = "root" + end + if password then + password = password:gsub("'", [['"'"']]) + end + + if username then + username = username:gsub("'", [['"'"']]) + end + + return os.execute( + "(echo '" .. password .. "'; sleep 1; echo '" .. password .. "') | " .. + "passwd '" .. username .. "' >/dev/null 2>&1" + ) +end + + +--- LuCI system utilities / wifi related functions. +-- @class module +-- @name luci.sys.wifi +wifi = {} + +--- Get wireless information for given interface. +-- @param ifname String containing the interface name +-- @return A wrapped iwinfo object instance +function wifi.getiwinfo(ifname) + local stat, iwinfo = pcall(require, "iwinfo") + + if ifname then + local c = 0 + local u = uci.cursor_state() + local d, n = ifname:match("^(%w+)%.network(%d+)") + if d and n then + ifname = d + n = tonumber(n) + u:foreach("wireless", "wifi-iface", + function(s) + if s.device == d then + c = c + 1 + if c == n then + ifname = s.ifname or s.device + return false + end + end + end) + elseif u:get("wireless", ifname) == "wifi-device" then + u:foreach("wireless", "wifi-iface", + function(s) + if s.device == ifname and s.ifname then + ifname = s.ifname + return false + end + end) + end + + local t = stat and iwinfo.type(ifname) + local x = t and iwinfo[t] or { } + return setmetatable({}, { + __index = function(t, k) + if k == "ifname" then + return ifname + elseif x[k] then + return x[k](ifname) + end + end + }) + end +end + + +--- LuCI system utilities / init related functions. +-- @class module +-- @name luci.sys.init +init = {} +init.dir = "/etc/init.d/" + +--- Get the names of all installed init scripts +-- @return Table containing the names of all inistalled init scripts +function init.names() + local names = { } + for name in fs.glob(init.dir.."*") do + names[#names+1] = fs.basename(name) + end + return names +end + +--- Get the index of he given init script +-- @param name Name of the init script +-- @return Numeric index value +function init.index(name) + if fs.access(init.dir..name) then + return call("env -i sh -c 'source %s%s enabled; exit ${START:-255}' >/dev/null" + %{ init.dir, name }) + end +end + +local function init_action(action, name) + if fs.access(init.dir..name) then + return call("env -i %s%s %s >/dev/null" %{ init.dir, name, action }) + end +end + +--- Test whether the given init script is enabled +-- @param name Name of the init script +-- @return Boolean indicating whether init is enabled +function init.enabled(name) + return (init_action("enabled", name) == 0) +end + +--- Enable the given init script +-- @param name Name of the init script +-- @return Boolean indicating success +function init.enable(name) + return (init_action("enable", name) == 1) +end + +--- Disable the given init script +-- @param name Name of the init script +-- @return Boolean indicating success +function init.disable(name) + return (init_action("disable", name) == 0) +end + +--- Start the given init script +-- @param name Name of the init script +-- @return Boolean indicating success +function init.start(name) + return (init_action("start", name) == 0) +end + +--- Stop the given init script +-- @param name Name of the init script +-- @return Boolean indicating success +function init.stop(name) + return (init_action("stop", name) == 0) +end + + +-- Internal functions + +function _parse_mixed_record(cnt, delimiter) + delimiter = delimiter or " " + local data = {} + local flags = {} + + for i, l in pairs(luci.util.split(luci.util.trim(cnt), "\n")) do + for j, f in pairs(luci.util.split(luci.util.trim(l), delimiter, nil, true)) do + local k, x, v = f:match('([^%s][^:=]*) *([:=]*) *"*([^\n"]*)"*') + + if k then + if x == "" then + table.insert(flags, k) + else + data[k] = v + end + end + end + end + + return data, flags +end diff --git a/Mi_Lua/luci/sys/iptparser.lua b/Mi_Lua/luci/sys/iptparser.lua new file mode 100644 index 0000000..90b00f2 --- /dev/null +++ b/Mi_Lua/luci/sys/iptparser.lua @@ -0,0 +1,371 @@ +--[[ + +Iptables parser and query library +(c) 2008-2009 Jo-Philipp Wich +(c) 2008-2009 Steven Barth + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +$Id: iptparser.lua 6826 2011-01-29 22:47:40Z jow $ + +]]-- + +local luci = {} +luci.util = require "luci.util" +luci.sys = require "luci.sys" +luci.ip = require "luci.ip" + +local tonumber, ipairs, table = tonumber, ipairs, table + +--- LuCI iptables parser and query library +-- @cstyle instance +module("luci.sys.iptparser") + +--- Create a new iptables parser object. +-- @class function +-- @name IptParser +-- @param family Number specifying the address family. 4 for IPv4, 6 for IPv6 +-- @return IptParser instance +IptParser = luci.util.class() + +function IptParser.__init__( self, family ) + self._family = (tonumber(family) == 6) and 6 or 4 + self._rules = { } + self._chains = { } + + if self._family == 4 then + self._nulladdr = "0.0.0.0/0" + self._tables = { "filter", "nat", "mangle", "raw" } + self._command = "iptables -t %s --line-numbers -nxvL" + else + self._nulladdr = "::/0" + self._tables = { "filter", "mangle", "raw" } + self._command = "ip6tables -t %s --line-numbers -nxvL" + end + + self:_parse_rules() +end + +--- Find all firewall rules that match the given criteria. Expects a table with +-- search criteria as only argument. If args is nil or an empty table then all +-- rules will be returned. +-- +-- The following keys in the args table are recognized: +--

    +--
  • table - Match rules that are located within the given table +--
  • chain - Match rules that are located within the given chain +--
  • target - Match rules with the given target +--
  • protocol - Match rules that match the given protocol, rules with +-- protocol "all" are always matched +--
  • source - Match rules with the given source, rules with source +-- "0.0.0.0/0" (::/0) are always matched +--
  • destination - Match rules with the given destination, rules with +-- destination "0.0.0.0/0" (::/0) are always matched +--
  • inputif - Match rules with the given input interface, rules +-- with input interface "*" (=all) are always matched +--
  • outputif - Match rules with the given output interface, rules +-- with output interface "*" (=all) are always matched +--
  • flags - Match rules that match the given flags, current +-- supported values are "-f" (--fragment) +-- and "!f" (! --fragment) +--
  • options - Match rules containing all given options +--
+-- The return value is a list of tables representing the matched rules. +-- Each rule table contains the following fields: +--
    +--
  • index - The index number of the rule +--
  • table - The table where the rule is located, can be one +-- of "filter", "nat" or "mangle" +--
  • chain - The chain where the rule is located, e.g. "INPUT" +-- or "postrouting_wan" +--
  • target - The rule target, e.g. "REJECT" or "DROP" +--
  • protocol The matching protocols, e.g. "all" or "tcp" +--
  • flags - Special rule options ("--", "-f" or "!f") +--
  • inputif - Input interface of the rule, e.g. "eth0.0" +-- or "*" for all interfaces +--
  • outputif - Output interface of the rule,e.g. "eth0.0" +-- or "*" for all interfaces +--
  • source - The source ip range, e.g. "0.0.0.0/0" (::/0) +--
  • destination - The destination ip range, e.g. "0.0.0.0/0" (::/0) +--
  • options - A list of specific options of the rule, +-- e.g. { "reject-with", "tcp-reset" } +--
  • packets - The number of packets matched by the rule +--
  • bytes - The number of total bytes matched by the rule +--
+-- Example: +--
+-- ip = luci.sys.iptparser.IptParser()
+-- result = ip.find( {
+-- 	target="REJECT",
+-- 	protocol="tcp",
+-- 	options={ "reject-with", "tcp-reset" }
+-- } )
+-- 
+-- This will match all rules with target "-j REJECT", +-- protocol "-p tcp" (or "-p all") +-- and the option "--reject-with tcp-reset". +-- @params args Table containing the search arguments (optional) +-- @return Table of matching rule tables +function IptParser.find( self, args ) + + local args = args or { } + local rv = { } + + args.source = args.source and self:_parse_addr(args.source) + args.destination = args.destination and self:_parse_addr(args.destination) + + for i, rule in ipairs(self._rules) do + local match = true + + -- match table + if not ( not args.table or args.table:lower() == rule.table ) then + match = false + end + + -- match chain + if not ( match == true and ( + not args.chain or args.chain == rule.chain + ) ) then + match = false + end + + -- match target + if not ( match == true and ( + not args.target or args.target == rule.target + ) ) then + match = false + end + + -- match protocol + if not ( match == true and ( + not args.protocol or rule.protocol == "all" or + args.protocol:lower() == rule.protocol + ) ) then + match = false + end + + -- match source + if not ( match == true and ( + not args.source or rule.source == self._nulladdr or + self:_parse_addr(rule.source):contains(args.source) + ) ) then + match = false + end + + -- match destination + if not ( match == true and ( + not args.destination or rule.destination == self._nulladdr or + self:_parse_addr(rule.destination):contains(args.destination) + ) ) then + match = false + end + + -- match input interface + if not ( match == true and ( + not args.inputif or rule.inputif == "*" or + args.inputif == rule.inputif + ) ) then + match = false + end + + -- match output interface + if not ( match == true and ( + not args.outputif or rule.outputif == "*" or + args.outputif == rule.outputif + ) ) then + match = false + end + + -- match flags (the "opt" column) + if not ( match == true and ( + not args.flags or rule.flags == args.flags + ) ) then + match = false + end + + -- match specific options + if not ( match == true and ( + not args.options or + self:_match_options( rule.options, args.options ) + ) ) then + match = false + end + + -- insert match + if match == true then + rv[#rv+1] = rule + end + end + + return rv +end + + +--- Rebuild the internal lookup table, for example when rules have changed +-- through external commands. +-- @return nothing +function IptParser.resync( self ) + self._rules = { } + self._chain = nil + self:_parse_rules() +end + + +--- Find the names of all tables. +-- @return Table of table names. +function IptParser.tables( self ) + return self._tables +end + + +--- Find the names of all chains within the given table name. +-- @param table String containing the table name +-- @return Table of chain names in the order they occur. +function IptParser.chains( self, table ) + local lookup = { } + local chains = { } + for _, r in ipairs(self:find({table=table})) do + if not lookup[r.chain] then + lookup[r.chain] = true + chains[#chains+1] = r.chain + end + end + return chains +end + + +--- Return the given firewall chain within the given table name. +-- @param table String containing the table name +-- @param chain String containing the chain name +-- @return Table containing the fields "policy", "packets", "bytes" +-- and "rules". The "rules" field is a table of rule tables. +function IptParser.chain( self, table, chain ) + return self._chains[table:lower()] and self._chains[table:lower()][chain] +end + + +--- Test whether the given target points to a custom chain. +-- @param target String containing the target action +-- @return Boolean indicating whether target is a custom chain. +function IptParser.is_custom_target( self, target ) + for _, r in ipairs(self._rules) do + if r.chain == target then + return true + end + end + return false +end + + +-- [internal] Parse address according to family. +function IptParser._parse_addr( self, addr ) + if self._family == 4 then + return luci.ip.IPv4(addr) + else + return luci.ip.IPv6(addr) + end +end + +-- [internal] Parse iptables output from all tables. +function IptParser._parse_rules( self ) + + for i, tbl in ipairs(self._tables) do + + self._chains[tbl] = { } + + for i, rule in ipairs(luci.util.execl(self._command % tbl)) do + + if rule:find( "^Chain " ) == 1 then + + local crefs + local cname, cpol, cpkt, cbytes = rule:match( + "^Chain ([^%s]*) %(policy (%w+) " .. + "(%d+) packets, (%d+) bytes%)" + ) + + if not cname then + cname, crefs = rule:match( + "^Chain ([^%s]*) %((%d+) references%)" + ) + end + + self._chain = cname + self._chains[tbl][cname] = { + policy = cpol, + packets = tonumber(cpkt or 0), + bytes = tonumber(cbytes or 0), + references = tonumber(crefs or 0), + rules = { } + } + + else + if rule:find("%d") == 1 then + + local rule_parts = luci.util.split( rule, "%s+", nil, true ) + local rule_details = { } + + -- cope with rules that have no target assigned + if rule:match("^%d+%s+%d+%s+%d+%s%s") then + table.insert(rule_parts, 4, nil) + end + + -- ip6tables opt column is usually zero-width + if self._family == 6 then + table.insert(rule_parts, 6, "--") + end + + rule_details["table"] = tbl + rule_details["chain"] = self._chain + rule_details["index"] = tonumber(rule_parts[1]) + rule_details["packets"] = tonumber(rule_parts[2]) + rule_details["bytes"] = tonumber(rule_parts[3]) + rule_details["target"] = rule_parts[4] + rule_details["protocol"] = rule_parts[5] + rule_details["flags"] = rule_parts[6] + rule_details["inputif"] = rule_parts[7] + rule_details["outputif"] = rule_parts[8] + rule_details["source"] = rule_parts[9] + rule_details["destination"] = rule_parts[10] + rule_details["options"] = { } + + for i = 11, #rule_parts - 1 do + rule_details["options"][i-10] = rule_parts[i] + end + + self._rules[#self._rules+1] = rule_details + + self._chains[tbl][self._chain].rules[ + #self._chains[tbl][self._chain].rules + 1 + ] = rule_details + end + end + end + end + + self._chain = nil +end + + +-- [internal] Return true if optlist1 contains all elements of optlist 2. +-- Return false in all other cases. +function IptParser._match_options( self, o1, o2 ) + + -- construct a hashtable of first options list to speed up lookups + local oh = { } + for i, opt in ipairs( o1 ) do oh[opt] = true end + + -- iterate over second options list + -- each string in o2 must be also present in o1 + -- if o2 contains a string which is not found in o1 then return false + for i, opt in ipairs( o2 ) do + if not oh[opt] then + return false + end + end + + return true +end diff --git a/Mi_Lua/luci/sys/zoneinfo.lua b/Mi_Lua/luci/sys/zoneinfo.lua new file mode 100644 index 0000000..f5a12bf --- /dev/null +++ b/Mi_Lua/luci/sys/zoneinfo.lua @@ -0,0 +1,28 @@ +--[[ +LuCI - Autogenerated Zoneinfo Module + +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 + +]]-- + +local setmetatable, require, rawget, rawset = setmetatable, require, rawget, rawset + +module "luci.sys.zoneinfo" + +setmetatable(_M, { + __index = function(t, k) + if k == "TZ" and not rawget(t, k) then + local m = require "luci.sys.zoneinfo.tzdata" + rawset(t, k, rawget(m, k)) + elseif k == "OFFSET" and not rawget(t, k) then + local m = require "luci.sys.zoneinfo.tzoffset" + rawset(t, k, rawget(m, k)) + end + + return rawget(t, k) + end +}) diff --git a/Mi_Lua/luci/sys/zoneinfo/tzdata.lua b/Mi_Lua/luci/sys/zoneinfo/tzdata.lua new file mode 100644 index 0000000..1a99f6a --- /dev/null +++ b/Mi_Lua/luci/sys/zoneinfo/tzdata.lua @@ -0,0 +1,420 @@ +--[[ +LuCI - Autogenerated Zoneinfo Module + +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 + +]]-- + +module "luci.sys.zoneinfo.tzdata" + +TZ = { + { 'Africa/Abidjan', 'GMT0' }, + { 'Africa/Accra', 'GMT0' }, + { 'Africa/Addis Ababa', 'EAT-3' }, + { 'Africa/Algiers', 'CET-1' }, + { 'Africa/Asmara', 'EAT-3' }, + { 'Africa/Bamako', 'GMT0' }, + { 'Africa/Bangui', 'WAT-1' }, + { 'Africa/Banjul', 'GMT0' }, + { 'Africa/Bissau', 'GMT0' }, + { 'Africa/Blantyre', 'CAT-2' }, + { 'Africa/Brazzaville', 'WAT-1' }, + { 'Africa/Bujumbura', 'CAT-2' }, + { 'Africa/Casablanca', 'WET0' }, + { 'Africa/Ceuta', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Africa/Conakry', 'GMT0' }, + { 'Africa/Dakar', 'GMT0' }, + { 'Africa/Dar es Salaam', 'EAT-3' }, + { 'Africa/Djibouti', 'EAT-3' }, + { 'Africa/Douala', 'WAT-1' }, + { 'Africa/El Aaiun', 'WET0' }, + { 'Africa/Freetown', 'GMT0' }, + { 'Africa/Gaborone', 'CAT-2' }, + { 'Africa/Harare', 'CAT-2' }, + { 'Africa/Johannesburg', 'SAST-2' }, + { 'Africa/Juba', 'EAT-3' }, + { 'Africa/Kampala', 'EAT-3' }, + { 'Africa/Khartoum', 'EAT-3' }, + { 'Africa/Kigali', 'CAT-2' }, + { 'Africa/Kinshasa', 'WAT-1' }, + { 'Africa/Lagos', 'WAT-1' }, + { 'Africa/Libreville', 'WAT-1' }, + { 'Africa/Lome', 'GMT0' }, + { 'Africa/Luanda', 'WAT-1' }, + { 'Africa/Lubumbashi', 'CAT-2' }, + { 'Africa/Lusaka', 'CAT-2' }, + { 'Africa/Malabo', 'WAT-1' }, + { 'Africa/Maputo', 'CAT-2' }, + { 'Africa/Maseru', 'SAST-2' }, + { 'Africa/Mbabane', 'SAST-2' }, + { 'Africa/Mogadishu', 'EAT-3' }, + { 'Africa/Monrovia', 'GMT0' }, + { 'Africa/Nairobi', 'EAT-3' }, + { 'Africa/Ndjamena', 'WAT-1' }, + { 'Africa/Niamey', 'WAT-1' }, + { 'Africa/Nouakchott', 'GMT0' }, + { 'Africa/Ouagadougou', 'GMT0' }, + { 'Africa/Porto-Novo', 'WAT-1' }, + { 'Africa/Sao Tome', 'GMT0' }, + { 'Africa/Tripoli', 'EET-2' }, + { 'Africa/Tunis', 'CET-1' }, + { 'Africa/Windhoek', 'WAT-1WAST,M9.1.0,M4.1.0' }, + { 'America/Adak', 'HAST10HADT,M3.2.0,M11.1.0' }, + { 'America/Anchorage', 'AKST9AKDT,M3.2.0,M11.1.0' }, + { 'America/Anguilla', 'AST4' }, + { 'America/Antigua', 'AST4' }, + { 'America/Araguaina', 'BRT3' }, + { 'America/Argentina/Buenos Aires', 'ART3' }, + { 'America/Argentina/Catamarca', 'ART3' }, + { 'America/Argentina/Cordoba', 'ART3' }, + { 'America/Argentina/Jujuy', 'ART3' }, + { 'America/Argentina/La Rioja', 'ART3' }, + { 'America/Argentina/Mendoza', 'ART3' }, + { 'America/Argentina/Rio Gallegos', 'ART3' }, + { 'America/Argentina/Salta', 'ART3' }, + { 'America/Argentina/San Juan', 'ART3' }, + { 'America/Argentina/Tucuman', 'ART3' }, + { 'America/Argentina/Ushuaia', 'ART3' }, + { 'America/Aruba', 'AST4' }, + { 'America/Asuncion', 'PYT4PYST,M10.1.0/0,M4.2.0/0' }, + { 'America/Atikokan', 'EST5' }, + { 'America/Bahia', 'BRT3BRST,M10.3.0/0,M2.3.0/0' }, + { 'America/Bahia Banderas', 'CST6CDT,M4.1.0,M10.5.0' }, + { 'America/Barbados', 'AST4' }, + { 'America/Belem', 'BRT3' }, + { 'America/Belize', 'CST6' }, + { 'America/Blanc-Sablon', 'AST4' }, + { 'America/Boa Vista', 'AMT4' }, + { 'America/Bogota', 'COT5' }, + { 'America/Boise', 'MST7MDT,M3.2.0,M11.1.0' }, + { 'America/Cambridge Bay', 'MST7MDT,M3.2.0,M11.1.0' }, + { 'America/Campo Grande', 'AMT4AMST,M10.3.0/0,M2.3.0/0' }, + { 'America/Cancun', 'CST6CDT,M4.1.0,M10.5.0' }, + { 'America/Caracas', 'VET4:30' }, + { 'America/Cayenne', 'GFT3' }, + { 'America/Cayman', 'EST5' }, + { 'America/Chicago', 'CST6CDT,M3.2.0,M11.1.0' }, + { 'America/Chihuahua', 'MST7MDT,M4.1.0,M10.5.0' }, + { 'America/Costa Rica', 'CST6' }, + { 'America/Cuiaba', 'AMT4AMST,M10.3.0/0,M2.3.0/0' }, + { 'America/Curacao', 'AST4' }, + { 'America/Danmarkshavn', 'GMT0' }, + { 'America/Dawson', 'PST8PDT,M3.2.0,M11.1.0' }, + { 'America/Dawson Creek', 'MST7' }, + { 'America/Denver', 'MST7MDT,M3.2.0,M11.1.0' }, + { 'America/Detroit', 'EST5EDT,M3.2.0,M11.1.0' }, + { 'America/Dominica', 'AST4' }, + { 'America/Edmonton', 'MST7MDT,M3.2.0,M11.1.0' }, + { 'America/Eirunepe', 'AMT4' }, + { 'America/El Salvador', 'CST6' }, + { 'America/Fortaleza', 'BRT3' }, + { 'America/Glace Bay', 'AST4ADT,M3.2.0,M11.1.0' }, + { 'America/Goose Bay', 'AST4ADT,M3.2.0,M11.1.0' }, + { 'America/Grand Turk', 'EST5EDT,M3.2.0,M11.1.0' }, + { 'America/Grenada', 'AST4' }, + { 'America/Guadeloupe', 'AST4' }, + { 'America/Guatemala', 'CST6' }, + { 'America/Guayaquil', 'ECT5' }, + { 'America/Guyana', 'GYT4' }, + { 'America/Halifax', 'AST4ADT,M3.2.0,M11.1.0' }, + { 'America/Havana', 'CST5CDT,M3.2.0/0,M10.5.0/1' }, + { 'America/Hermosillo', 'MST7' }, + { 'America/Indiana/Indianapolis', 'EST5EDT,M3.2.0,M11.1.0' }, + { 'America/Indiana/Knox', 'CST6CDT,M3.2.0,M11.1.0' }, + { 'America/Indiana/Marengo', 'EST5EDT,M3.2.0,M11.1.0' }, + { 'America/Indiana/Petersburg', 'EST5EDT,M3.2.0,M11.1.0' }, + { 'America/Indiana/Tell City', 'CST6CDT,M3.2.0,M11.1.0' }, + { 'America/Indiana/Vevay', 'EST5EDT,M3.2.0,M11.1.0' }, + { 'America/Indiana/Vincennes', 'EST5EDT,M3.2.0,M11.1.0' }, + { 'America/Indiana/Winamac', 'EST5EDT,M3.2.0,M11.1.0' }, + { 'America/Inuvik', 'MST7MDT,M3.2.0,M11.1.0' }, + { 'America/Iqaluit', 'EST5EDT,M3.2.0,M11.1.0' }, + { 'America/Jamaica', 'EST5' }, + { 'America/Juneau', 'AKST9AKDT,M3.2.0,M11.1.0' }, + { 'America/Kentucky/Louisville', 'EST5EDT,M3.2.0,M11.1.0' }, + { 'America/Kentucky/Monticello', 'EST5EDT,M3.2.0,M11.1.0' }, + { 'America/Kralendijk', 'AST4' }, + { 'America/La Paz', 'BOT4' }, + { 'America/Lima', 'PET5' }, + { 'America/Los Angeles', 'PST8PDT,M3.2.0,M11.1.0' }, + { 'America/Lower Princes', 'AST4' }, + { 'America/Maceio', 'BRT3' }, + { 'America/Managua', 'CST6' }, + { 'America/Manaus', 'AMT4' }, + { 'America/Marigot', 'AST4' }, + { 'America/Martinique', 'AST4' }, + { 'America/Matamoros', 'CST6CDT,M3.2.0,M11.1.0' }, + { 'America/Mazatlan', 'MST7MDT,M4.1.0,M10.5.0' }, + { 'America/Menominee', 'CST6CDT,M3.2.0,M11.1.0' }, + { 'America/Merida', 'CST6CDT,M4.1.0,M10.5.0' }, + { 'America/Metlakatla', 'MeST8' }, + { 'America/Mexico City', 'CST6CDT,M4.1.0,M10.5.0' }, + { 'America/Miquelon', 'PMST3PMDT,M3.2.0,M11.1.0' }, + { 'America/Moncton', 'AST4ADT,M3.2.0,M11.1.0' }, + { 'America/Monterrey', 'CST6CDT,M4.1.0,M10.5.0' }, + { 'America/Montevideo', 'UYT3UYST,M10.1.0,M3.2.0' }, + { 'America/Montreal', 'EST5EDT,M3.2.0,M11.1.0' }, + { 'America/Montserrat', 'AST4' }, + { 'America/Nassau', 'EST5EDT,M3.2.0,M11.1.0' }, + { 'America/New York', 'EST5EDT,M3.2.0,M11.1.0' }, + { 'America/Nipigon', 'EST5EDT,M3.2.0,M11.1.0' }, + { 'America/Nome', 'AKST9AKDT,M3.2.0,M11.1.0' }, + { 'America/Noronha', 'FNT2' }, + { 'America/North Dakota/Beulah', 'CST6CDT,M3.2.0,M11.1.0' }, + { 'America/North Dakota/Center', 'CST6CDT,M3.2.0,M11.1.0' }, + { 'America/North Dakota/New Salem', 'CST6CDT,M3.2.0,M11.1.0' }, + { 'America/Ojinaga', 'MST7MDT,M3.2.0,M11.1.0' }, + { 'America/Panama', 'EST5' }, + { 'America/Pangnirtung', 'EST5EDT,M3.2.0,M11.1.0' }, + { 'America/Paramaribo', 'SRT3' }, + { 'America/Phoenix', 'MST7' }, + { 'America/Port of Spain', 'AST4' }, + { 'America/Port-au-Prince', 'EST5' }, + { 'America/Porto Velho', 'AMT4' }, + { 'America/Puerto Rico', 'AST4' }, + { 'America/Rainy River', 'CST6CDT,M3.2.0,M11.1.0' }, + { 'America/Rankin Inlet', 'CST6CDT,M3.2.0,M11.1.0' }, + { 'America/Recife', 'BRT3' }, + { 'America/Regina', 'CST6' }, + { 'America/Resolute', 'CST6CDT,M3.2.0,M11.1.0' }, + { 'America/Rio Branco', 'AMT4' }, + { 'America/Santa Isabel', 'PST8PDT,M4.1.0,M10.5.0' }, + { 'America/Santarem', 'BRT3' }, + { 'America/Santo Domingo', 'AST4' }, + { 'America/Sao Paulo', 'BRT3BRST,M10.3.0/0,M2.3.0/0' }, + { 'America/Scoresbysund', 'EGT1EGST,M3.5.0/0,M10.5.0/1' }, + { 'America/Shiprock', 'MST7MDT,M3.2.0,M11.1.0' }, + { 'America/Sitka', 'AKST9AKDT,M3.2.0,M11.1.0' }, + { 'America/St Barthelemy', 'AST4' }, + { 'America/St Johns', 'NST3:30NDT,M3.2.0,M11.1.0' }, + { 'America/St Kitts', 'AST4' }, + { 'America/St Lucia', 'AST4' }, + { 'America/St Thomas', 'AST4' }, + { 'America/St Vincent', 'AST4' }, + { 'America/Swift Current', 'CST6' }, + { 'America/Tegucigalpa', 'CST6' }, + { 'America/Thule', 'AST4ADT,M3.2.0,M11.1.0' }, + { 'America/Thunder Bay', 'EST5EDT,M3.2.0,M11.1.0' }, + { 'America/Tijuana', 'PST8PDT,M3.2.0,M11.1.0' }, + { 'America/Toronto', 'EST5EDT,M3.2.0,M11.1.0' }, + { 'America/Tortola', 'AST4' }, + { 'America/Vancouver', 'PST8PDT,M3.2.0,M11.1.0' }, + { 'America/Whitehorse', 'PST8PDT,M3.2.0,M11.1.0' }, + { 'America/Winnipeg', 'CST6CDT,M3.2.0,M11.1.0' }, + { 'America/Yakutat', 'AKST9AKDT,M3.2.0,M11.1.0' }, + { 'America/Yellowknife', 'MST7MDT,M3.2.0,M11.1.0' }, + { 'Antarctica/Casey', 'WST-8' }, + { 'Antarctica/Davis', 'DAVT-7' }, + { 'Antarctica/DumontDUrville', 'DDUT-10' }, + { 'Antarctica/Macquarie', 'MIST-11' }, + { 'Antarctica/Mawson', 'MAWT-5' }, + { 'Antarctica/McMurdo', 'NZST-12NZDT,M9.5.0,M4.1.0/3' }, + { 'Antarctica/Rothera', 'ROTT3' }, + { 'Antarctica/South Pole', 'NZST-12NZDT,M9.5.0,M4.1.0/3' }, + { 'Antarctica/Syowa', 'SYOT-3' }, + { 'Antarctica/Vostok', 'VOST-6' }, + { 'Arctic/Longyearbyen', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Asia/Aden', 'AST-3' }, + { 'Asia/Almaty', 'ALMT-6' }, + { 'Asia/Anadyr', 'ANAT-12' }, + { 'Asia/Aqtau', 'AQTT-5' }, + { 'Asia/Aqtobe', 'AQTT-5' }, + { 'Asia/Ashgabat', 'TMT-5' }, + { 'Asia/Baghdad', 'AST-3' }, + { 'Asia/Bahrain', 'AST-3' }, + { 'Asia/Baku', 'AZT-4AZST,M3.5.0/4,M10.5.0/5' }, + { 'Asia/Bangkok', 'ICT-7' }, + { 'Asia/Beirut', 'EET-2EEST,M3.5.0/0,M10.5.0/0' }, + { 'Asia/Bishkek', 'KGT-6' }, + { 'Asia/Brunei', 'BNT-8' }, + { 'Asia/Choibalsan', 'CHOT-8' }, + { 'Asia/Chongqing', 'CST-8' }, + { 'Asia/Colombo', 'IST-5:30' }, + { 'Asia/Damascus', 'EET-2EEST,M4.1.5/0,M10.5.5/0' }, + { 'Asia/Dhaka', 'BDT-6' }, + { 'Asia/Dili', 'TLT-9' }, + { 'Asia/Dubai', 'GST-4' }, + { 'Asia/Dushanbe', 'TJT-5' }, + { 'Asia/Gaza', 'EET-2' }, + { 'Asia/Harbin', 'CST-8' }, + { 'Asia/Hebron', 'EET-2' }, + { 'Asia/Ho Chi Minh', 'ICT-7' }, + { 'Asia/Hong Kong', 'HKT-8' }, + { 'Asia/Hovd', 'HOVT-7' }, + { 'Asia/Irkutsk', 'IRKT-9' }, + { 'Asia/Jakarta', 'WIT-7' }, + { 'Asia/Jayapura', 'EIT-9' }, + { 'Asia/Kabul', 'AFT-4:30' }, + { 'Asia/Kamchatka', 'PETT-12' }, + { 'Asia/Karachi', 'PKT-5' }, + { 'Asia/Kashgar', 'CST-8' }, + { 'Asia/Kathmandu', 'NPT-5:45' }, + { 'Asia/Kolkata', 'IST-5:30' }, + { 'Asia/Krasnoyarsk', 'KRAT-8' }, + { 'Asia/Kuala Lumpur', 'MYT-8' }, + { 'Asia/Kuching', 'MYT-8' }, + { 'Asia/Kuwait', 'AST-3' }, + { 'Asia/Macau', 'CST-8' }, + { 'Asia/Magadan', 'MAGT-12' }, + { 'Asia/Makassar', 'CIT-8' }, + { 'Asia/Manila', 'PHT-8' }, + { 'Asia/Muscat', 'GST-4' }, + { 'Asia/Nicosia', 'EET-2EEST,M3.5.0/3,M10.5.0/4' }, + { 'Asia/Novokuznetsk', 'NOVT-7' }, + { 'Asia/Novosibirsk', 'NOVT-7' }, + { 'Asia/Omsk', 'OMST-7' }, + { 'Asia/Oral', 'ORAT-5' }, + { 'Asia/Phnom Penh', 'ICT-7' }, + { 'Asia/Pontianak', 'WIT-7' }, + { 'Asia/Pyongyang', 'KST-9' }, + { 'Asia/Qatar', 'AST-3' }, + { 'Asia/Qyzylorda', 'QYZT-6' }, + { 'Asia/Rangoon', 'MMT-6:30' }, + { 'Asia/Riyadh', 'AST-3' }, + { 'Asia/Sakhalin', 'SAKT-11' }, + { 'Asia/Samarkand', 'UZT-5' }, + { 'Asia/Seoul', 'KST-9' }, + { 'Asia/Shanghai', 'CST-8' }, + { 'Asia/Singapore', 'SGT-8' }, + { 'Asia/Taipei', 'CST-8' }, + { 'Asia/Tashkent', 'UZT-5' }, + { 'Asia/Tbilisi', 'GET-4' }, + { 'Asia/Thimphu', 'BTT-6' }, + { 'Asia/Tokyo', 'JST-9' }, + { 'Asia/Ulaanbaatar', 'ULAT-8' }, + { 'Asia/Urumqi', 'CST-8' }, + { 'Asia/Vientiane', 'ICT-7' }, + { 'Asia/Vladivostok', 'VLAT-11' }, + { 'Asia/Yakutsk', 'YAKT-10' }, + { 'Asia/Yekaterinburg', 'YEKT-6' }, + { 'Asia/Yerevan', 'AMT-4AMST,M3.5.0,M10.5.0/3' }, + { 'Atlantic/Azores', 'AZOT1AZOST,M3.5.0/0,M10.5.0/1' }, + { 'Atlantic/Bermuda', 'AST4ADT,M3.2.0,M11.1.0' }, + { 'Atlantic/Canary', 'WET0WEST,M3.5.0/1,M10.5.0' }, + { 'Atlantic/Cape Verde', 'CVT1' }, + { 'Atlantic/Faroe', 'WET0WEST,M3.5.0/1,M10.5.0' }, + { 'Atlantic/Madeira', 'WET0WEST,M3.5.0/1,M10.5.0' }, + { 'Atlantic/Reykjavik', 'GMT0' }, + { 'Atlantic/South Georgia', 'GST2' }, + { 'Atlantic/St Helena', 'GMT0' }, + { 'Atlantic/Stanley', 'FKT4FKST,M9.1.0,M4.3.0' }, + { 'Australia/Adelaide', 'CST-9:30CST,M10.1.0,M4.1.0/3' }, + { 'Australia/Brisbane', 'EST-10' }, + { 'Australia/Broken Hill', 'CST-9:30CST,M10.1.0,M4.1.0/3' }, + { 'Australia/Currie', 'EST-10EST,M10.1.0,M4.1.0/3' }, + { 'Australia/Darwin', 'CST-9:30' }, + { 'Australia/Eucla', 'CWST-8:45' }, + { 'Australia/Hobart', 'EST-10EST,M10.1.0,M4.1.0/3' }, + { 'Australia/Lindeman', 'EST-10' }, + { 'Australia/Lord Howe', 'LHST-10:30LHST-11,M10.1.0,M4.1.0' }, + { 'Australia/Melbourne', 'EST-10EST,M10.1.0,M4.1.0/3' }, + { 'Australia/Perth', 'WST-8' }, + { 'Australia/Sydney', 'EST-10EST,M10.1.0,M4.1.0/3' }, + { 'Europe/Amsterdam', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Andorra', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Athens', 'EET-2EEST,M3.5.0/3,M10.5.0/4' }, + { 'Europe/Belgrade', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Berlin', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Bratislava', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Brussels', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Bucharest', 'EET-2EEST,M3.5.0/3,M10.5.0/4' }, + { 'Europe/Budapest', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Chisinau', 'EET-2EEST,M3.5.0/3,M10.5.0/4' }, + { 'Europe/Copenhagen', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Dublin', 'GMT0IST,M3.5.0/1,M10.5.0' }, + { 'Europe/Gibraltar', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Guernsey', 'GMT0BST,M3.5.0/1,M10.5.0' }, + { 'Europe/Helsinki', 'EET-2EEST,M3.5.0/3,M10.5.0/4' }, + { 'Europe/Isle of Man', 'GMT0BST,M3.5.0/1,M10.5.0' }, + { 'Europe/Istanbul', 'EET-2EEST,M3.5.0/3,M10.5.0/4' }, + { 'Europe/Jersey', 'GMT0BST,M3.5.0/1,M10.5.0' }, + { 'Europe/Kaliningrad', 'FET-3' }, + { 'Europe/Kiev', 'EET-2EEST,M3.5.0/3,M10.5.0/4' }, + { 'Europe/Lisbon', 'WET0WEST,M3.5.0/1,M10.5.0' }, + { 'Europe/Ljubljana', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/London', 'GMT0BST,M3.5.0/1,M10.5.0' }, + { 'Europe/Luxembourg', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Madrid', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Malta', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Mariehamn', 'EET-2EEST,M3.5.0/3,M10.5.0/4' }, + { 'Europe/Minsk', 'FET-3' }, + { 'Europe/Monaco', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Moscow', 'MSK-4' }, + { 'Europe/Oslo', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Paris', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Podgorica', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Prague', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Riga', 'EET-2EEST,M3.5.0/3,M10.5.0/4' }, + { 'Europe/Rome', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Samara', 'SAMT-4' }, + { 'Europe/San Marino', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Sarajevo', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Simferopol', 'EET-2EEST,M3.5.0/3,M10.5.0/4' }, + { 'Europe/Skopje', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Sofia', 'EET-2EEST,M3.5.0/3,M10.5.0/4' }, + { 'Europe/Stockholm', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Tallinn', 'EET-2EEST,M3.5.0/3,M10.5.0/4' }, + { 'Europe/Tirane', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Uzhgorod', 'EET-2EEST,M3.5.0/3,M10.5.0/4' }, + { 'Europe/Vaduz', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Vatican', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Vienna', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Vilnius', 'EET-2EEST,M3.5.0/3,M10.5.0/4' }, + { 'Europe/Volgograd', 'VOLT-4' }, + { 'Europe/Warsaw', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Zagreb', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Europe/Zaporozhye', 'EET-2EEST,M3.5.0/3,M10.5.0/4' }, + { 'Europe/Zurich', 'CET-1CEST,M3.5.0,M10.5.0/3' }, + { 'Indian/Antananarivo', 'EAT-3' }, + { 'Indian/Chagos', 'IOT-6' }, + { 'Indian/Christmas', 'CXT-7' }, + { 'Indian/Cocos', 'CCT-6:30' }, + { 'Indian/Comoro', 'EAT-3' }, + { 'Indian/Kerguelen', 'TFT-5' }, + { 'Indian/Mahe', 'SCT-4' }, + { 'Indian/Maldives', 'MVT-5' }, + { 'Indian/Mauritius', 'MUT-4' }, + { 'Indian/Mayotte', 'EAT-3' }, + { 'Indian/Reunion', 'RET-4' }, + { 'Pacific/Apia', 'WST-13' }, + { 'Pacific/Auckland', 'NZST-12NZDT,M9.5.0,M4.1.0/3' }, + { 'Pacific/Chatham', 'CHAST-12:45CHADT,M9.5.0/2:45,M4.1.0/3:45' }, + { 'Pacific/Chuuk', 'CHUT-10' }, + { 'Pacific/Efate', 'VUT-11' }, + { 'Pacific/Enderbury', 'PHOT-13' }, + { 'Pacific/Fakaofo', 'TKT10' }, + { 'Pacific/Fiji', 'FJT-12' }, + { 'Pacific/Funafuti', 'TVT-12' }, + { 'Pacific/Galapagos', 'GALT6' }, + { 'Pacific/Gambier', 'GAMT9' }, + { 'Pacific/Guadalcanal', 'SBT-11' }, + { 'Pacific/Guam', 'ChST-10' }, + { 'Pacific/Honolulu', 'HST10' }, + { 'Pacific/Johnston', 'HST10' }, + { 'Pacific/Kiritimati', 'LINT-14' }, + { 'Pacific/Kosrae', 'KOST-11' }, + { 'Pacific/Kwajalein', 'MHT-12' }, + { 'Pacific/Majuro', 'MHT-12' }, + { 'Pacific/Marquesas', 'MART9:30' }, + { 'Pacific/Midway', 'SST11' }, + { 'Pacific/Nauru', 'NRT-12' }, + { 'Pacific/Niue', 'NUT11' }, + { 'Pacific/Norfolk', 'NFT-11:30' }, + { 'Pacific/Noumea', 'NCT-11' }, + { 'Pacific/Pago Pago', 'SST11' }, + { 'Pacific/Palau', 'PWT-9' }, + { 'Pacific/Pitcairn', 'PST8' }, + { 'Pacific/Pohnpei', 'PONT-11' }, + { 'Pacific/Port Moresby', 'PGT-10' }, + { 'Pacific/Rarotonga', 'CKT10' }, + { 'Pacific/Saipan', 'ChST-10' }, + { 'Pacific/Tahiti', 'TAHT10' }, + { 'Pacific/Tarawa', 'GILT-12' }, + { 'Pacific/Tongatapu', 'TOT-13' }, + { 'Pacific/Wake', 'WAKT-12' }, + { 'Pacific/Wallis', 'WFT-12' }, +} diff --git a/Mi_Lua/luci/sys/zoneinfo/tzoffset.lua b/Mi_Lua/luci/sys/zoneinfo/tzoffset.lua new file mode 100644 index 0000000..bbe75d5 --- /dev/null +++ b/Mi_Lua/luci/sys/zoneinfo/tzoffset.lua @@ -0,0 +1,162 @@ +--[[ +LuCI - Autogenerated Zoneinfo Module + +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 + +]]-- + +module "luci.sys.zoneinfo.tzoffset" + +OFFSET = { + gmt = 0, -- GMT + eat = 10800, -- EAT + cet = 3600, -- CET + wat = 3600, -- WAT + cat = 7200, -- CAT + wet = 0, -- WET + sast = 7200, -- SAST + eet = 7200, -- EET + hast = -36000, -- HAST + hadt = -32400, -- HADT + akst = -32400, -- AKST + akdt = -28800, -- AKDT + ast = -14400, -- AST + brt = -10800, -- BRT + art = -10800, -- ART + pyt = -14400, -- PYT + pyst = -10800, -- PYST + est = -18000, -- EST + cst = -21600, -- CST + cdt = -18000, -- CDT + amt = -14400, -- AMT + cot = -18000, -- COT + mst = -25200, -- MST + mdt = -21600, -- MDT + vet = -16200, -- VET + gft = -10800, -- GFT + pst = -28800, -- PST + pdt = -25200, -- PDT + ect = -18000, -- ECT + gyt = -14400, -- GYT + bot = -14400, -- BOT + pet = -18000, -- PET + pmst = -10800, -- PMST + pmdt = -7200, -- PMDT + uyt = -10800, -- UYT + uyst = -7200, -- UYST + fnt = -7200, -- FNT + srt = -10800, -- SRT + egt = -3600, -- EGT + egst = 0, -- EGST + nst = -12600, -- NST + ndt = -9000, -- NDT + wst = 28800, -- WST + davt = 25200, -- DAVT + ddut = 36000, -- DDUT + mist = 39600, -- MIST + mawt = 18000, -- MAWT + nzst = 43200, -- NZST + nzdt = 46800, -- NZDT + rott = -10800, -- ROTT + syot = 10800, -- SYOT + vost = 21600, -- VOST + almt = 21600, -- ALMT + anat = 43200, -- ANAT + aqtt = 18000, -- AQTT + tmt = 18000, -- TMT + azt = 14400, -- AZT + azst = 18000, -- AZST + ict = 25200, -- ICT + kgt = 21600, -- KGT + bnt = 28800, -- BNT + chot = 28800, -- CHOT + ist = 19800, -- IST + bdt = 21600, -- BDT + tlt = 32400, -- TLT + gst = 14400, -- GST + tjt = 18000, -- TJT + hkt = 28800, -- HKT + hovt = 25200, -- HOVT + irkt = 32400, -- IRKT + wit = 25200, -- WIT + eit = 32400, -- EIT + aft = 16200, -- AFT + pett = 43200, -- PETT + pkt = 18000, -- PKT + npt = 20700, -- NPT + krat = 28800, -- KRAT + myt = 28800, -- MYT + magt = 43200, -- MAGT + cit = 28800, -- CIT + pht = 28800, -- PHT + novt = 25200, -- NOVT + omst = 25200, -- OMST + orat = 18000, -- ORAT + kst = 32400, -- KST + qyzt = 21600, -- QYZT + mmt = 23400, -- MMT + sakt = 39600, -- SAKT + uzt = 18000, -- UZT + sgt = 28800, -- SGT + get = 14400, -- GET + btt = 21600, -- BTT + jst = 32400, -- JST + ulat = 28800, -- ULAT + vlat = 39600, -- VLAT + yakt = 36000, -- YAKT + yekt = 21600, -- YEKT + azot = -3600, -- AZOT + azost = 0, -- AZOST + cvt = -3600, -- CVT + fkt = -14400, -- FKT + fkst = -10800, -- FKST + cwst = 31500, -- CWST + lhst = 37800, -- LHST + lhst = 39600, -- LHST + fet = 10800, -- FET + msk = 14400, -- MSK + samt = 14400, -- SAMT + volt = 14400, -- VOLT + iot = 21600, -- IOT + cxt = 25200, -- CXT + cct = 23400, -- CCT + tft = 18000, -- TFT + sct = 14400, -- SCT + mvt = 18000, -- MVT + mut = 14400, -- MUT + ret = 14400, -- RET + chast = 45900, -- CHAST + chadt = 49500, -- CHADT + chut = 36000, -- CHUT + vut = 39600, -- VUT + phot = 46800, -- PHOT + tkt = -36000, -- TKT + fjt = 43200, -- FJT + tvt = 43200, -- TVT + galt = -21600, -- GALT + gamt = -32400, -- GAMT + sbt = 39600, -- SBT + hst = -36000, -- HST + lint = 50400, -- LINT + kost = 39600, -- KOST + mht = 43200, -- MHT + mart = -34200, -- MART + sst = -39600, -- SST + nrt = 43200, -- NRT + nut = -39600, -- NUT + nft = 41400, -- NFT + nct = 39600, -- NCT + pwt = 32400, -- PWT + pont = 39600, -- PONT + pgt = 36000, -- PGT + ckt = -36000, -- CKT + taht = -36000, -- TAHT + gilt = 43200, -- GILT + tot = 46800, -- TOT + wakt = 43200, -- WAKT + wft = 43200, -- WFT +} diff --git a/Mi_Lua/luci/template.lua b/Mi_Lua/luci/template.lua new file mode 100644 index 0000000..4c1816a --- /dev/null +++ b/Mi_Lua/luci/template.lua @@ -0,0 +1,107 @@ +--[[ +LuCI - Template Parser + +Description: +A template parser supporting includes, translations, Lua code blocks +and more. It can be used either as a compiler or as an interpreter. + +FileId: $Id: template.lua 9558 2012-12-18 13:58:22Z jow $ + +License: +Copyright 2008 Steven Barth + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +]]-- + +local util = require "luci.util" +local config = require "luci.config" +local tparser = require "luci.template.parser" + +local tostring, pairs, loadstring = tostring, pairs, loadstring +local setmetatable, loadfile = setmetatable, loadfile +local getfenv, setfenv, rawget = getfenv, setfenv, rawget +local assert, type, error = assert, type, error + +--- LuCI template library. +module "luci.template" + +config.template = config.template or {} +viewdir = config.template.viewdir or util.libpath() .. "/view" + + +-- Define the namespace for template modules +context = util.threadlocal() + +--- Render a certain template. +-- @param name Template name +-- @param scope Scope to assign to template (optional) +function render(name, scope) + return Template(name):render(scope or getfenv(2)) +end + + +-- Template class +Template = util.class() + +-- Shared template cache to store templates in to avoid unnecessary reloading +Template.cache = setmetatable({}, {__mode = "v"}) + + +-- Constructor - Reads and compiles the template on-demand +function Template.__init__(self, name) + + self.template = self.cache[name] + self.name = name + + -- Create a new namespace for this template + self.viewns = context.viewns + + -- If we have a cached template, skip compiling and loading + if not self.template then + + -- Compile template + local err + local sourcefile = viewdir .. "/" .. name .. ".htm" + + self.template, _, err = tparser.parse(sourcefile) + + -- If we have no valid template throw error, otherwise cache the template + if not self.template then + error("Failed to load template '" .. name .. "'.\n" .. + "Error while parsing template '" .. sourcefile .. "':\n" .. + (err or "Unknown syntax error")) + else + self.cache[name] = self.template + end + end +end + + +-- Renders a template +function Template.render(self, scope) + scope = scope or getfenv(2) + + -- Put our predefined objects in the scope of the template + setfenv(self.template, setmetatable({}, {__index = + function(tbl, key) + return rawget(tbl, key) or self.viewns[key] or scope[key] + end})) + + -- Now finally render the thing + local stat, err = util.copcall(self.template) + if not stat then + error("Failed to execute template '" .. self.name .. "'.\n" .. + "A runtime error occured: " .. tostring(err or "(nil)")) + end +end diff --git a/Mi_Lua/luci/template/parser.so b/Mi_Lua/luci/template/parser.so new file mode 100644 index 0000000000000000000000000000000000000000..18d8b7a0289caf2f40f908752fbf609f6f37d977 GIT binary patch literal 14188 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 NgBavD!wN8@`zKOM2oL}O literal 0 HcmV?d00001 diff --git a/Mi_Lua/luci/tools/firewall.lua b/Mi_Lua/luci/tools/firewall.lua new file mode 100644 index 0000000..26240aa --- /dev/null +++ b/Mi_Lua/luci/tools/firewall.lua @@ -0,0 +1,291 @@ +--[[ +LuCI - Lua Configuration Interface + +Copyright 2011-2012 Jo-Philipp Wich + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +]]-- + +module("luci.tools.firewall", package.seeall) + +local ut = require "luci.util" +local ip = require "luci.ip" +local nx = require "nixio" + +local translate, translatef = luci.i18n.translate, luci.i18n.translatef + +local function tr(...) + return tostring(translate(...)) +end + +function fmt_neg(x) + if type(x) == "string" then + local v, neg = x:gsub("^ *! *", "") + if neg > 0 then + return v, "%s " % tr("not") + else + return x, "" + end + end + return x, "" +end + +function fmt_mac(x) + if x and #x > 0 then + local m, n + local l = { tr("MAC"), " " } + for m in ut.imatch(x) do + m, n = fmt_neg(m) + l[#l+1] = "%s%s" %{ n, m } + l[#l+1] = ", " + end + if #l > 1 then + l[#l] = nil + if #l > 3 then + l[1] = tr("MACs") + end + return table.concat(l, "") + end + end +end + +function fmt_port(x, d) + if x and #x > 0 then + local p, n + local l = { tr("port"), " " } + for p in ut.imatch(x) do + p, n = fmt_neg(p) + local a, b = p:match("(%d+)%D+(%d+)") + if a and b then + l[1] = tr("ports") + l[#l+1] = "%s%d-%d" %{ n, a, b } + else + l[#l+1] = "%s%d" %{ n, p } + end + l[#l+1] = ", " + end + if #l > 1 then + l[#l] = nil + if #l > 3 then + l[1] = tr("ports") + end + return table.concat(l, "") + end + end + return d and "%s" % d +end + +function fmt_ip(x, d) + if x and #x > 0 then + local l = { tr("IP"), " " } + local v, a, n + for v in ut.imatch(x) do + v, n = fmt_neg(v) + a, m = v:match("(%S+)/(%d+%.%S+)") + a = a or v + a = a:match(":") and ip.IPv6(a, m) or ip.IPv4(a, m) + if a and (a:is6() and a:prefix() < 128 or a:prefix() < 32) then + l[1] = tr("IP range") + l[#l+1] = "%s%s" %{ + a:minhost():string(), + a:maxhost():string(), + n, a:string() + } + else + l[#l+1] = "%s%s" %{ + n, + a and a:string() or v + } + end + l[#l+1] = ", " + end + if #l > 1 then + l[#l] = nil + if #l > 3 then + l[1] = tr("IPs") + end + return table.concat(l, "") + end + end + return d and "%s" % d +end + +function fmt_zone(x, d) + if x == "*" then + return "%s" % tr("any zone") + elseif x and #x > 0 then + return "%s" % x + elseif d then + return "%s" % d + end +end + +function fmt_icmp_type(x) + if x and #x > 0 then + local t, v, n + local l = { tr("type"), " " } + for v in ut.imatch(x) do + v, n = fmt_neg(v) + l[#l+1] = "%s%s" %{ n, v } + l[#l+1] = ", " + end + if #l > 1 then + l[#l] = nil + if #l > 3 then + l[1] = tr("types") + end + return table.concat(l, "") + end + end +end + +function fmt_proto(x, icmp_types) + if x and #x > 0 then + local v, n + local l = { } + local t = fmt_icmp_type(icmp_types) + for v in ut.imatch(x) do + v, n = fmt_neg(v) + if v == "tcpudp" then + l[#l+1] = "TCP" + l[#l+1] = ", " + l[#l+1] = "UDP" + l[#l+1] = ", " + elseif v ~= "all" then + local p = nx.getproto(v) + if p then + -- ICMP + if (p.proto == 1 or p.proto == 58) and t then + l[#l+1] = translatef( + "%s%s with %s", + n, p.aliases[1] or p.name, t + ) + else + l[#l+1] = "%s%s" %{ + n, + p.aliases[1] or p.name + } + end + l[#l+1] = ", " + end + end + end + if #l > 0 then + l[#l] = nil + return table.concat(l, "") + end + end +end + +function fmt_limit(limit, burst) + burst = tonumber(burst) + if limit and #limit > 0 then + local l, u = limit:match("(%d+)/(%w+)") + l = tonumber(l or limit) + u = u or "second" + if l then + if u:match("^s") then + u = tr("second") + elseif u:match("^m") then + u = tr("minute") + elseif u:match("^h") then + u = tr("hour") + elseif u:match("^d") then + u = tr("day") + end + if burst and burst > 0 then + return translatef("%d pkts. per %s, \ + burst %d pkts.", l, u, burst) + else + return translatef("%d pkts. per %s", l, u) + end + end + end +end + +function fmt_target(x, dest) + if dest and #dest > 0 then + if x == "ACCEPT" then + return tr("Accept forward") + elseif x == "REJECT" then + return tr("Refuse forward") + elseif x == "NOTRACK" then + return tr("Do not track forward") + else --if x == "DROP" then + return tr("Discard forward") + end + else + if x == "ACCEPT" then + return tr("Accept input") + elseif x == "REJECT" then + return tr("Refuse input") + elseif x == "NOTRACK" then + return tr("Do not track input") + else --if x == "DROP" then + return tr("Discard input") + end + end +end + + +function opt_enabled(s, t, ...) + if t == luci.cbi.Button then + local o = s:option(t, "__enabled") + function o.render(self, section) + if self.map:get(section, "enabled") ~= "0" then + self.title = tr("Rule is enabled") + self.inputtitle = tr("Disable") + self.inputstyle = "reset" + else + self.title = tr("Rule is disabled") + self.inputtitle = tr("Enable") + self.inputstyle = "apply" + end + t.render(self, section) + end + function o.write(self, section, value) + if self.map:get(section, "enabled") ~= "0" then + self.map:set(section, "enabled", "0") + else + self.map:del(section, "enabled") + end + end + return o + else + local o = s:option(t, "enabled", ...) + o.enabled = "" + o.disabled = "0" + o.default = o.enabled + return o + end +end + +function opt_name(s, t, ...) + local o = s:option(t, "name", ...) + + function o.cfgvalue(self, section) + return self.map:get(section, "name") or + self.map:get(section, "_name") or "-" + end + + function o.write(self, section, value) + if value ~= "-" then + self.map:set(section, "name", value) + self.map:del(section, "_name") + else + self:remove(section) + end + end + + function o.remove(self, section) + self.map:del(section, "name") + self.map:del(section, "_name") + end + + return o +end diff --git a/Mi_Lua/luci/tools/proto.lua b/Mi_Lua/luci/tools/proto.lua new file mode 100644 index 0000000..4df0269 --- /dev/null +++ b/Mi_Lua/luci/tools/proto.lua @@ -0,0 +1,46 @@ +--[[ +LuCI - Lua Configuration Interface + +Copyright 2012 Jo-Philipp Wich + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +]]-- + +module("luci.tools.proto", package.seeall) + +function opt_macaddr(s, ifc, ...) + local v = luci.cbi.Value + local o = s:taboption("advanced", v, "macaddr", ...) + + o.placeholder = ifc and ifc:mac() + o.datatype = "macaddr" + + function o.cfgvalue(self, section) + local w = ifc and ifc:get_wifinet() + if w then + return w:get("macaddr") + else + return v.cfgvalue(self, section) + end + end + + function o.write(self, section, value) + local w = ifc and ifc:get_wifinet() + if w then + w:set("macaddr", value) + elseif value then + v.write(self, section, value) + else + v.remove(self, section) + end + end + + function o.remove(self, section) + self:write(section, nil) + end +end diff --git a/Mi_Lua/luci/tools/status.lua b/Mi_Lua/luci/tools/status.lua new file mode 100644 index 0000000..13d3ea4 --- /dev/null +++ b/Mi_Lua/luci/tools/status.lua @@ -0,0 +1,216 @@ +--[[ +LuCI - Lua Configuration Interface + +Copyright 2011 Jo-Philipp Wich + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +]]-- + +module("luci.tools.status", package.seeall) + +local uci = require "luci.model.uci".cursor() + +local function dhcp_leases_common(family) + local rv = { } + local nfs = require "nixio.fs" + local leasefile = "/var/dhcp.leases" + + uci:foreach("dhcp", "dnsmasq", + function(s) + if s.leasefile and nfs.access(s.leasefile) then + leasefile = s.leasefile + return false + end + end) + + local fd = io.open(leasefile, "r") + if fd then + while true do + local ln = fd:read("*l") + if not ln then + break + else + local ts, mac, ip, name, duid = ln:match("^(%d+) (%S+) (%S+) (%S+) (%S+)") + if ts and mac and ip and name and duid then + if family == 4 and not ip:match(":") then + rv[#rv+1] = { + expires = os.difftime(tonumber(ts) or 0, os.time()), + macaddr = mac, + ipaddr = ip, + hostname = (name ~= "*") and name + } + elseif family == 6 and ip:match(":") then + rv[#rv+1] = { + expires = os.difftime(tonumber(ts) or 0, os.time()), + ip6addr = ip, + duid = (duid ~= "*") and duid, + hostname = (name ~= "*") and name + } + end + end + end + end + fd:close() + end + + return rv +end + +function dhcp_leases() + return dhcp_leases_common(4) +end + +function dhcp6_leases() + local nfs = require "nixio.fs" + local leasefile = "/tmp/hosts/6relayd" + local rv = {} + + if nfs.access(leasefile, "r") then + local fd = io.open(leasefile, "r") + if fd then + while true do + local ln = fd:read("*l") + if not ln then + break + else + local iface, duid, iaid, name, ts, id, length, ip = ln:match("^# (%S+) (%S+) (%S+) (%S+) (%d+) (%S+) (%S+) (.*)") + if ip then + rv[#rv+1] = { + expires = os.difftime(tonumber(ts) or 0, os.time()), + duid = duid, + ip6addr = ip, + hostname = (name ~= "-") and name + } + end + end + end + fd:close() + end + return rv + elseif luci.sys.call("dnsmasq --version 2>/dev/null | grep -q ' DHCPv6 '") == 0 then + return dhcp_leases_common(6) + end +end + +function wifi_networks() + local rv = { } + local ntm = require "luci.model.network".init() + + local dev + for _, dev in ipairs(ntm:get_wifidevs()) do + local rd = { + up = dev:is_up(), + device = dev:name(), + name = dev:get_i18n(), + networks = { } + } + + local net + for _, net in ipairs(dev:get_wifinets()) do + rd.networks[#rd.networks+1] = { + name = net:shortname(), + link = net:adminlink(), + up = net:is_up(), + mode = net:active_mode(), + ssid = net:active_ssid(), + bssid = net:active_bssid(), + encryption = net:active_encryption(), + frequency = net:frequency(), + channel = net:channel(), + signal = net:signal(), + quality = net:signal_percent(), + noise = net:noise(), + bitrate = net:bitrate(), + ifname = net:ifname(), + assoclist = net:assoclist(), + country = net:country(), + txpower = net:txpower(), + txpoweroff = net:txpower_offset() + } + end + + rv[#rv+1] = rd + end + + return rv +end + +function wifi_network(id) + local ntm = require "luci.model.network".init() + local net = ntm:get_wifinet(id) + if net then + local dev = net:get_device() + if dev then + return { + id = id, + name = net:shortname(), + link = net:adminlink(), + up = net:is_up(), + mode = net:active_mode(), + ssid = net:active_ssid(), + bssid = net:active_bssid(), + encryption = net:active_encryption(), + frequency = net:frequency(), + channel = net:channel(), + signal = net:signal(), + quality = net:signal_percent(), + noise = net:noise(), + bitrate = net:bitrate(), + ifname = net:ifname(), + assoclist = net:assoclist(), + country = net:country(), + txpower = net:txpower(), + txpoweroff = net:txpower_offset(), + device = { + up = dev:is_up(), + device = dev:name(), + name = dev:get_i18n() + } + } + end + end + return { } +end + +function switch_status(devs) + local dev + local switches = { } + for dev in devs:gmatch("[^%s,]+") do + local ports = { } + local swc = io.popen("swconfig dev %q show" % dev, "r") + if swc then + local l + repeat + l = swc:read("*l") + if l then + local port, up = l:match("port:(%d+) link:(%w+)") + if port then + local speed = l:match(" speed:(%d+)") + local duplex = l:match(" (%w+)-duplex") + local txflow = l:match(" (txflow)") + local rxflow = l:match(" (rxflow)") + local auto = l:match(" (auto)") + + ports[#ports+1] = { + port = tonumber(port) or 0, + speed = tonumber(speed) or 0, + link = (up == "up"), + duplex = (duplex == "full"), + rxflow = (not not rxflow), + txflow = (not not txflow), + auto = (not not auto) + } + end + end + until not l + swc:close() + end + switches[dev] = ports + end + return switches +end diff --git a/Mi_Lua/luci/tools/webadmin.lua b/Mi_Lua/luci/tools/webadmin.lua new file mode 100644 index 0000000..11232ae --- /dev/null +++ b/Mi_Lua/luci/tools/webadmin.lua @@ -0,0 +1,173 @@ +--[[ +LuCI - Lua Configuration Interface + +Copyright 2008 Steven Barth +Copyright 2008 Jo-Philipp Wich + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +$Id: webadmin.lua 4168 2009-01-27 17:15:14Z jow $ +]]-- + +module("luci.tools.webadmin", package.seeall) +local uci = require("luci.model.uci") +require("luci.sys") +require("luci.ip") + +function byte_format(byte) + local suff = {"B", "KB", "MB", "GB", "TB"} + for i=1, 5 do + if byte > 1024 and i < 5 then + byte = byte / 1024 + else + return string.format("%.2f %s", byte, suff[i]) + end + end +end + +function date_format(secs) + local suff = {"min", "h", "d"} + local mins = 0 + local hour = 0 + local days = 0 + + secs = math.floor(secs) + if secs > 60 then + mins = math.floor(secs / 60) + secs = secs % 60 + end + + if mins > 60 then + hour = math.floor(mins / 60) + mins = mins % 60 + end + + if hour > 24 then + days = math.floor(hour / 24) + hour = hour % 24 + end + + if days > 0 then + return string.format("%.0fd %02.0fh %02.0fmin %02.0fs", days, hour, mins, secs) + else + return string.format("%02.0fh %02.0fmin %02.0fs", hour, mins, secs) + end +end + +function network_get_addresses(net) + local state = uci.cursor_state() + state:load("network") + local addr = {} + local ipv4 = state:get("network", net, "ipaddr") + local mav4 = state:get("network", net, "netmask") + local ipv6 = state:get("network", net, "ip6addr") + + if ipv4 and #ipv4 > 0 then + if mav4 and #mav4 == 0 then mav4 = nil end + + ipv4 = luci.ip.IPv4(ipv4, mav4) + + if ipv4 then + table.insert(addr, ipv4:string()) + end + end + + if ipv6 then + table.insert(addr, ipv6) + end + + state:foreach("network", "alias", + function (section) + if section.interface == net then + if section.ipaddr and section.netmask then + local ipv4 = luci.ip.IPv4(section.ipaddr, section.netmask) + + if ipv4 then + table.insert(addr, ipv4:string()) + end + end + + if section.ip6addr then + table.insert(addr, section.ip6addr) + end + end + end + ) + + return addr +end + +function cbi_add_networks(field) + uci.cursor():foreach("network", "interface", + function (section) + if section[".name"] ~= "loopback" then + field:value(section[".name"]) + end + end + ) + field.titleref = luci.dispatcher.build_url("admin", "network", "network") +end + +function cbi_add_knownips(field) + for i, dataset in ipairs(luci.sys.net.arptable()) do + field:value(dataset["IP address"]) + end +end + +function network_get_zones(net) + local state = uci.cursor_state() + if not state:load("firewall") then + return nil + end + + local zones = {} + + state:foreach("firewall", "zone", + function (section) + local znet = section.network or section.name + if luci.util.contains(luci.util.split(znet, " "), net) then + table.insert(zones, section.name) + end + end + ) + + return zones +end + +function firewall_find_zone(name) + local find + + luci.model.uci.cursor():foreach("firewall", "zone", + function (section) + if section.name == name then + find = section[".name"] + end + end + ) + + return find +end + +function iface_get_network(iface) + local state = uci.cursor_state() + state:load("network") + local net + + state:foreach("network", "interface", + function (section) + local ifname = state:get( + "network", section[".name"], "ifname" + ) + + if iface == ifname then + net = section[".name"] + end + end + ) + + return net +end diff --git a/Mi_Lua/luci/util.lua b/Mi_Lua/luci/util.lua new file mode 100644 index 0000000..6260856 --- /dev/null +++ b/Mi_Lua/luci/util.lua @@ -0,0 +1,855 @@ +--[[ +LuCI - Utility library + +Description: +Several common useful Lua functions + +License: +Copyright 2008 Steven Barth + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +]]-- + +local io = require "io" +local math = require "math" +local table = require "table" +local debug = require "debug" +local ldebug = require "luci.debug" +local string = require "string" +local coroutine = require "coroutine" +local tparser = require "luci.template.parser" + +local getmetatable, setmetatable = getmetatable, setmetatable +local rawget, rawset, unpack = rawget, rawset, unpack +local tostring, type, assert = tostring, type, assert +local ipairs, pairs, next, loadstring = ipairs, pairs, next, loadstring +local require, pcall, xpcall = require, pcall, xpcall +local collectgarbage, get_memory_limit = collectgarbage, get_memory_limit + +--- LuCI utility functions. +module "luci.util" + +-- +-- Pythonic string formatting extension +-- +getmetatable("").__mod = function(a, b) + if not b then + return a + elseif type(b) == "table" then + for k, _ in pairs(b) do if type(b[k]) == "userdata" then b[k] = tostring(b[k]) end end + return a:format(unpack(b)) + else + if type(b) == "userdata" then b = tostring(b) end + return a:format(b) + end +end + + +-- +-- Class helper routines +-- + +-- Instantiates a class +local function _instantiate(class, ...) + local inst = setmetatable({}, {__index = class}) + + if inst.__init__ then + inst:__init__(...) + end + + return inst +end + +--- Create a Class object (Python-style object model). +-- The class object can be instantiated by calling itself. +-- Any class functions or shared parameters can be attached to this object. +-- Attaching a table to the class object makes this table shared between +-- all instances of this class. For object parameters use the __init__ function. +-- Classes can inherit member functions and values from a base class. +-- Class can be instantiated by calling them. All parameters will be passed +-- to the __init__ function of this class - if such a function exists. +-- The __init__ function must be used to set any object parameters that are not shared +-- with other objects of this class. Any return values will be ignored. +-- @param base The base class to inherit from (optional) +-- @return A class object +-- @see instanceof +-- @see clone +function class(base) + return setmetatable({}, { + __call = _instantiate, + __index = base + }) +end + +--- Test whether the given object is an instance of the given class. +-- @param object Object instance +-- @param class Class object to test against +-- @return Boolean indicating whether the object is an instance +-- @see class +-- @see clone +function instanceof(object, class) + local meta = getmetatable(object) + while meta and meta.__index do + if meta.__index == class then + return true + end + meta = getmetatable(meta.__index) + end + return false +end + + +-- +-- Scope manipulation routines +-- + +local tl_meta = { + __mode = "k", + + __index = function(self, key) + local t = rawget(self, coxpt[coroutine.running()] + or coroutine.running() or 0) + return t and t[key] + end, + + __newindex = function(self, key, value) + local c = coxpt[coroutine.running()] or coroutine.running() or 0 + if not rawget(self, c) then + rawset(self, c, { [key] = value }) + else + rawget(self, c)[key] = value + end + end +} + +--- Create a new or get an already existing thread local store associated with +-- the current active coroutine. A thread local store is private a table object +-- whose values can't be accessed from outside of the running coroutine. +-- @return Table value representing the corresponding thread local store +function threadlocal(tbl) + return setmetatable(tbl or {}, tl_meta) +end + + +-- +-- Debugging routines +-- + +--- Write given object to stderr. +-- @param obj Value to write to stderr +-- @return Boolean indicating whether the write operation was successful +function perror(obj) + return io.stderr:write(tostring(obj) .. "\n") +end + +--- Recursively dumps a table to stdout, useful for testing and debugging. +-- @param t Table value to dump +-- @param maxdepth Maximum depth +-- @return Always nil +function dumptable(t, maxdepth, i, seen) + i = i or 0 + seen = seen or setmetatable({}, {__mode="k"}) + + for k,v in pairs(t) do + perror(string.rep("\t", i) .. tostring(k) .. "\t" .. tostring(v)) + if type(v) == "table" and (not maxdepth or i < maxdepth) then + if not seen[v] then + seen[v] = true + dumptable(v, maxdepth, i+1, seen) + else + perror(string.rep("\t", i) .. "*** RECURSION ***") + end + end + end +end + + +-- +-- String and data manipulation routines +-- + +--- Create valid XML PCDATA from given string. +-- @param value String value containing the data to escape +-- @return String value containing the escaped data +function pcdata(value) + return value and tparser.pcdata(tostring(value)) +end + +--- Strip HTML tags from given string. +-- @param value String containing the HTML text +-- @return String with HTML tags stripped of +function striptags(value) + return value and tparser.striptags(tostring(value)) +end + +--- Splits given string on a defined separator sequence and return a table +-- containing the resulting substrings. The optional max parameter specifies +-- the number of bytes to process, regardless of the actual length of the given +-- string. The optional last parameter, regex, specifies whether the separator +-- sequence is interpreted as regular expression. +-- @param str String value containing the data to split up +-- @param pat String with separator pattern (optional, defaults to "\n") +-- @param max Maximum times to split (optional) +-- @param regex Boolean indicating whether to interpret the separator +-- pattern as regular expression (optional, default is false) +-- @return Table containing the resulting substrings +function split(str, pat, max, regex) + pat = pat or "\n" + max = max or #str + + local t = {} + local c = 1 + + if #str == 0 then + return {""} + end + + if #pat == 0 then + return nil + end + + if max == 0 then + return str + end + + repeat + local s, e = str:find(pat, c, not regex) + max = max - 1 + if s and max < 0 then + t[#t+1] = str:sub(c) + else + t[#t+1] = str:sub(c, s and s - 1) + end + c = e and e + 1 or #str + 1 + until not s or max < 0 + + return t +end + +--- Remove leading and trailing whitespace from given string value. +-- @param str String value containing whitespace padded data +-- @return String value with leading and trailing space removed +function trim(str) + return (str:gsub("^%s*(.-)%s*$", "%1")) +end + +--- Count the occurences of given substring in given string. +-- @param str String to search in +-- @param pattern String containing pattern to find +-- @return Number of found occurences +function cmatch(str, pat) + local count = 0 + for _ in str:gmatch(pat) do count = count + 1 end + return count +end + +--- Return a matching iterator for the given value. The iterator will return +-- one token per invocation, the tokens are separated by whitespace. If the +-- input value is a table, it is transformed into a string first. A nil value +-- will result in a valid interator which aborts with the first invocation. +-- @param val The value to scan (table, string or nil) +-- @return Iterator which returns one token per call +function imatch(v) + if type(v) == "table" then + local k = nil + return function() + k = next(v, k) + return v[k] + end + + elseif type(v) == "number" or type(v) == "boolean" then + local x = true + return function() + if x then + x = false + return tostring(v) + end + end + + elseif type(v) == "userdata" or type(v) == "string" then + return tostring(v):gmatch("%S+") + end + + return function() end +end + +--- Parse certain units from the given string and return the canonical integer +-- value or 0 if the unit is unknown. Upper- or lower case is irrelevant. +-- Recognized units are: +-- o "y" - one year (60*60*24*366) +-- o "m" - one month (60*60*24*31) +-- o "w" - one week (60*60*24*7) +-- o "d" - one day (60*60*24) +-- o "h" - one hour (60*60) +-- o "min" - one minute (60) +-- o "kb" - one kilobyte (1024) +-- o "mb" - one megabyte (1024*1024) +-- o "gb" - one gigabyte (1024*1024*1024) +-- o "kib" - one si kilobyte (1000) +-- o "mib" - one si megabyte (1000*1000) +-- o "gib" - one si gigabyte (1000*1000*1000) +-- @param ustr String containing a numerical value with trailing unit +-- @return Number containing the canonical value +function parse_units(ustr) + + local val = 0 + + -- unit map + local map = { + -- date stuff + y = 60 * 60 * 24 * 366, + m = 60 * 60 * 24 * 31, + w = 60 * 60 * 24 * 7, + d = 60 * 60 * 24, + h = 60 * 60, + min = 60, + + -- storage sizes + kb = 1024, + mb = 1024 * 1024, + gb = 1024 * 1024 * 1024, + + -- storage sizes (si) + kib = 1000, + mib = 1000 * 1000, + gib = 1000 * 1000 * 1000 + } + + -- parse input string + for spec in ustr:lower():gmatch("[0-9%.]+[a-zA-Z]*") do + + local num = spec:gsub("[^0-9%.]+$","") + local spn = spec:gsub("^[0-9%.]+", "") + + if map[spn] or map[spn:sub(1,1)] then + val = val + num * ( map[spn] or map[spn:sub(1,1)] ) + else + val = val + num + end + end + + + return val +end + +-- also register functions above in the central string class for convenience +string.pcdata = pcdata +string.striptags = striptags +string.split = split +string.trim = trim +string.cmatch = cmatch +string.parse_units = parse_units + + +--- Appends numerically indexed tables or single objects to a given table. +-- @param src Target table +-- @param ... Objects to insert +-- @return Target table +function append(src, ...) + for i, a in ipairs({...}) do + if type(a) == "table" then + for j, v in ipairs(a) do + src[#src+1] = v + end + else + src[#src+1] = a + end + end + return src +end + +--- Combines two or more numerically indexed tables and single objects into one table. +-- @param tbl1 Table value to combine +-- @param tbl2 Table value to combine +-- @param ... More tables to combine +-- @return Table value containing all values of given tables +function combine(...) + return append({}, ...) +end + +--- Checks whether the given table contains the given value. +-- @param table Table value +-- @param value Value to search within the given table +-- @return Boolean indicating whether the given value occurs within table +function contains(table, value) + for k, v in pairs(table) do + if value == v then + return k + end + end + return false +end + +--- Update values in given table with the values from the second given table. +-- Both table are - in fact - merged together. +-- @param t Table which should be updated +-- @param updates Table containing the values to update +-- @return Always nil +function update(t, updates) + for k, v in pairs(updates) do + t[k] = v + end +end + +--- Retrieve all keys of given associative table. +-- @param t Table to extract keys from +-- @return Sorted table containing the keys +function keys(t) + local keys = { } + if t then + for k, _ in kspairs(t) do + keys[#keys+1] = k + end + end + return keys +end + +--- Clones the given object and return it's copy. +-- @param object Table value to clone +-- @param deep Boolean indicating whether to do recursive cloning +-- @return Cloned table value +function clone(object, deep) + local copy = {} + + for k, v in pairs(object) do + if deep and type(v) == "table" then + v = clone(v, deep) + end + copy[k] = v + end + + return setmetatable(copy, getmetatable(object)) +end + + +--- Create a dynamic table which automatically creates subtables. +-- @return Dynamic Table +function dtable() + return setmetatable({}, { __index = + function(tbl, key) + return rawget(tbl, key) + or rawget(rawset(tbl, key, dtable()), key) + end + }) +end + + +-- Serialize the contents of a table value. +function _serialize_table(t) + local data = "" + local idata = "" + local ilen = 0 + + for k, v in pairs(t) do + if type(k) ~= "number" or k < 1 or math.floor(k) ~= k or ( k - #t ) > 3 then + k = _serialize_data(k) + v = _serialize_data(v) + data = data .. ( #data > 0 and ", " or "" ) .. + '[' .. k .. '] = ' .. v + elseif k > ilen then + ilen = k + end + end + + for i = 1, ilen do + local v = _serialize_data(t[i]) + idata = idata .. ( #idata > 0 and ", " or "" ) .. v + end + + return idata .. ( #data > 0 and #idata > 0 and ", " or "" ) .. data +end + +--- Recursively serialize given data to lua code, suitable for restoring +-- with loadstring(). +-- @param val Value containing the data to serialize +-- @return String value containing the serialized code +-- @see restore_data +-- @see get_bytecode +function serialize_data(val) + assert(not hasRecursion(val), "Recursion detected.") + return _serialize_data(val) +end + +function _serialize_data(val) + if val == nil then + return "nil" + elseif type(val) == "number" then + return val + elseif type(val) == "string" then + return "%q" % val + elseif type(val) == "boolean" then + return val and "true" or "false" + elseif type(val) == "function" then + return "loadstring(%q)" % get_bytecode(val) + elseif type(val) == "table" then + return "{ " .. _serialize_table(val) .. " }" + else + return '"[unhandled data type:' .. type(val) .. ']"' + end +end + +-- Check weather a table has Recursion, if true, it cant be serialized +function hasRecursion(t) + if t == nil or type(t) ~= "table" then + return false + end + + local seen = {} + -- add root to seen + seen[t] = true + return hasR(t, seen) +end + +function hasR(t, seen) + for k, v in pairs(t) do + if type(k) == "table" then + if seen[k] then + -- check is recursion + local tmp = t + while true do + if tmp == k then + return true + else + tmp = seen[tmp] + if not tmp then + break + end + end + end + -- check end + end + + seen[k] = t + if hasR(k, seen) then + return true + end + end + + if type(v) == "table" then + if seen[v] then + -- check is recursion + local tmp = t + while true do + if tmp == v then + return true + else + tmp = seen[tmp] + if not tmp then + break + end + end + end + -- check end + end + + seen[v] = t + if hasR(v, seen) then + return true + end + end + end + + return false +end + +--- Restore data previously serialized with serialize_data(). +-- @param str String containing the data to restore +-- @return Value containing the restored data structure +-- @see serialize_data +-- @see get_bytecode +function restore_data(str) + return loadstring("return " .. str)() +end + + +-- +-- Byte code manipulation routines +-- + +--- Return the current runtime bytecode of the given data. The byte code +-- will be stripped before it is returned. +-- @param val Value to return as bytecode +-- @return String value containing the bytecode of the given data +function get_bytecode(val) + local code + + if type(val) == "function" then + code = string.dump(val) + else + code = string.dump( loadstring( "return " .. serialize_data(val) ) ) + end + + return code -- and strip_bytecode(code) +end + +--- Strips unnescessary lua bytecode from given string. Information like line +-- numbers and debugging numbers will be discarded. Original version by +-- Peter Cawley (http://lua-users.org/lists/lua-l/2008-02/msg01158.html) +-- @param code String value containing the original lua byte code +-- @return String value containing the stripped lua byte code +function strip_bytecode(code) + local version, format, endian, int, size, ins, num, lnum = code:byte(5, 12) + local subint + if endian == 1 then + subint = function(code, i, l) + local val = 0 + for n = l, 1, -1 do + val = val * 256 + code:byte(i + n - 1) + end + return val, i + l + end + else + subint = function(code, i, l) + local val = 0 + for n = 1, l, 1 do + val = val * 256 + code:byte(i + n - 1) + end + return val, i + l + end + end + + local function strip_function(code) + local count, offset = subint(code, 1, size) + local stripped = { string.rep("\0", size) } + local dirty = offset + count + offset = offset + count + int * 2 + 4 + offset = offset + int + subint(code, offset, int) * ins + count, offset = subint(code, offset, int) + for n = 1, count do + local t + t, offset = subint(code, offset, 1) + if t == 1 then + offset = offset + 1 + elseif t == 4 then + offset = offset + size + subint(code, offset, size) + elseif t == 3 then + offset = offset + num + elseif t == 254 or t == 9 then + offset = offset + lnum + end + end + count, offset = subint(code, offset, int) + stripped[#stripped+1] = code:sub(dirty, offset - 1) + for n = 1, count do + local proto, off = strip_function(code:sub(offset, -1)) + stripped[#stripped+1] = proto + offset = offset + off - 1 + end + offset = offset + subint(code, offset, int) * int + int + count, offset = subint(code, offset, int) + for n = 1, count do + offset = offset + subint(code, offset, size) + size + int * 2 + end + count, offset = subint(code, offset, int) + for n = 1, count do + offset = offset + subint(code, offset, size) + size + end + stripped[#stripped+1] = string.rep("\0", int * 3) + return table.concat(stripped), offset + end + + return code:sub(1,12) .. strip_function(code:sub(13,-1)) +end + + +-- +-- Sorting iterator functions +-- + +function _sortiter( t, f ) + local keys = { } + + local k, v + for k, v in pairs(t) do + keys[#keys+1] = k + end + + local _pos = 0 + + table.sort( keys, f ) + + return function() + _pos = _pos + 1 + if _pos <= #keys then + return keys[_pos], t[keys[_pos]], _pos + end + end +end + +--- Return a key, value iterator which returns the values sorted according to +-- the provided callback function. +-- @param t The table to iterate +-- @param f A callback function to decide the order of elements +-- @return Function value containing the corresponding iterator +function spairs(t,f) + return _sortiter( t, f ) +end + +--- Return a key, value iterator for the given table. +-- The table pairs are sorted by key. +-- @param t The table to iterate +-- @return Function value containing the corresponding iterator +function kspairs(t) + return _sortiter( t ) +end + +--- Return a key, value iterator for the given table. +-- The table pairs are sorted by value. +-- @param t The table to iterate +-- @return Function value containing the corresponding iterator +function vspairs(t) + return _sortiter( t, function (a,b) return t[a] < t[b] end ) +end + + +-- +-- System utility functions +-- + +--- Test whether the current system is operating in big endian mode. +-- @return Boolean value indicating whether system is big endian +function bigendian() + return string.byte(string.dump(function() end), 7) == 0 +end + +--- Execute given commandline and gather stdout. +-- @param command String containing command to execute +-- @return String containing the command's stdout +function exec(command) + local pp = io.popen(command) + local data = pp:read("*a") + pp:close() + + return data +end + +--- Return a line-buffered iterator over the output of given command. +-- @param command String containing the command to execute +-- @return Iterator +function execi(command) + local pp = io.popen(command) + + return pp and function() + local line = pp:read() + + if not line then + pp:close() + end + + return line + end +end + +-- Deprecated +function execl(command) + local pp = io.popen(command) + local line = "" + local data = {} + + while true do + line = pp:read() + if (line == nil) then break end + data[#data+1] = line + end + pp:close() + + return data +end + +--- Returns the absolute path to LuCI base directory. +-- @return String containing the directory path +function libpath() + return require "nixio.fs".dirname(ldebug.__file__) +end + + +-- +-- Coroutine safe xpcall and pcall versions modified for Luci +-- original version: +-- coxpcall 1.13 - Copyright 2005 - Kepler Project (www.keplerproject.org) +-- +-- Copyright © 2005 Kepler Project. +-- Permission is hereby granted, free of charge, to any person obtaining a +-- copy of this software and associated documentation files (the "Software"), +-- to deal in the Software without restriction, including without limitation +-- the rights to use, copy, modify, merge, publish, distribute, sublicense, +-- and/or sell copies of the Software, and to permit persons to whom the +-- Software is furnished to do so, subject to the following conditions: +-- +-- The above copyright notice and this permission notice shall be +-- included in all copies or substantial portions of the Software. +-- +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +-- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +-- OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +-- IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +-- DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +-- TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE +-- OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +local performResume, handleReturnValue +local oldpcall, oldxpcall = pcall, xpcall +coxpt = {} +setmetatable(coxpt, {__mode = "kv"}) + +-- Identity function for copcall +local function copcall_id(trace, ...) + return ... +end + +--- This is a coroutine-safe drop-in replacement for Lua's "xpcall"-function +-- @param f Lua function to be called protected +-- @param err Custom error handler +-- @param ... Parameters passed to the function +-- @return A boolean whether the function call succeeded and the return +-- values of either the function or the error handler +function coxpcall(f, err, ...) + local res, co = oldpcall(coroutine.create, f) + if not res then + local params = {...} + local newf = function() return f(unpack(params)) end + co = coroutine.create(newf) + end + local c = coroutine.running() + coxpt[co] = coxpt[c] or c or 0 + + return performResume(err, co, ...) +end + +--- This is a coroutine-safe drop-in replacement for Lua's "pcall"-function +-- @param f Lua function to be called protected +-- @param ... Parameters passed to the function +-- @return A boolean whether the function call succeeded and the returns +-- values of the function or the error object +function copcall(f, ...) + return coxpcall(f, copcall_id, ...) +end + +-- Handle return value of protected call +function handleReturnValue(err, co, status, ...) + if not status then + return false, err(debug.traceback(co, (...)), ...) + end + + if coroutine.status(co) ~= 'suspended' then + return true, ... + end + + return performResume(err, co, coroutine.yield(...)) +end + +-- Resume execution of protected function call +function performResume(err, co, ...) + return handleReturnValue(err, co, coroutine.resume(co, ...)) +end diff --git a/Mi_Lua/luci/version.lua b/Mi_Lua/luci/version.lua new file mode 100644 index 0000000..2a45ce2 --- /dev/null +++ b/Mi_Lua/luci/version.lua @@ -0,0 +1,14 @@ +local pcall, dofile, _G = pcall, dofile, _G + +module "luci.version" + +if pcall(dofile, "/etc/openwrt_release") and _G.DISTRIB_DESCRIPTION then + distname = "" + distversion = _G.DISTRIB_DESCRIPTION +else + distname = "OpenWrt Firmware" + distversion = "Attitude Adjustment (r40348)" +end + +luciname = "LuCI 0.11.1 Release" +luciversion = "0.11.1" diff --git a/Mi_Lua/luci/view/error404.htm b/Mi_Lua/luci/view/error404.htm new file mode 100644 index 0000000..c0c7662 --- /dev/null +++ b/Mi_Lua/luci/view/error404.htm @@ -0,0 +1,19 @@ +<%# +LuCI - Lua Configuration Interface +Copyright 2008 Steven Barth +Copyright 2008 Jo-Philipp Wich + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +$Id: error404.htm 9014 2012-08-14 13:08:18Z jow $ + +-%> +<%+header%> +

404 <%:Not Found%>

+

<%:Sorry, the object you requested was not found.%>

+<%:Unable to dispatch%>: <%=luci.http.request.env.PATH_INFO%> +<%+footer%> diff --git a/Mi_Lua/luci/view/error500.htm b/Mi_Lua/luci/view/error500.htm new file mode 100644 index 0000000..b00c4c3 --- /dev/null +++ b/Mi_Lua/luci/view/error500.htm @@ -0,0 +1,19 @@ +<%# +LuCI - Lua Configuration Interface +Copyright 2008 Steven Barth +Copyright 2008 Jo-Philipp Wich + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +$Id: error500.htm 9014 2012-08-14 13:08:18Z jow $ + +-%> +<%+header%> +

500 <%:Internal Server Error%>

+

<%:Sorry, the server encountered an unexpected error.%>

+
<%=message%>
+<%+footer%> diff --git a/Mi_Lua/luci/view/firewall/cbi_addforward.htm b/Mi_Lua/luci/view/firewall/cbi_addforward.htm new file mode 100644 index 0000000..3726f64 --- /dev/null +++ b/Mi_Lua/luci/view/firewall/cbi_addforward.htm @@ -0,0 +1,115 @@ +<%- + local fw = require "luci.model.firewall".init() + local izl = { } + local ezl = { } + local _, z + for _, z in ipairs(fw:get_zones()) do + if z:name() ~= "wan" then + izl[#izl+1] = z + elseif z:name() ~= "lan" then + ezl[#ezl+1] = z + end + end +-%> +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
<%:New port forward%>:
<%:Name%><%:Protocol%><%:External zone%><%:External port%><%:Internal zone%><%:Internal IP address%><%:Internal port%>
+ + + + + + + + + + + + + + + +
+ + +
diff --git a/Mi_Lua/luci/view/firewall/cbi_addrule.htm b/Mi_Lua/luci/view/firewall/cbi_addrule.htm new file mode 100644 index 0000000..463b2e0 --- /dev/null +++ b/Mi_Lua/luci/view/firewall/cbi_addrule.htm @@ -0,0 +1,112 @@ +<% + local fw = require "luci.model.firewall".init() + local wz = fw:get_zone("wan") + local lz = fw:get_zone("lan") +%> + +
+ <% if wz and lz then %> +
+ + + + + + + + + + + + + + + + +
<%:Open ports on router%>:
<%:Name%><%:Protocol%><%:External port%>
+ + + + + + + +
+ + + + + + + + + + + + + + + + + +

<%:New forward rule%>:
<%:Name%><%:Source zone%><%:Destination zone%>
+ + + + + + + +
+ + + <% else %> + + <% end %> +
diff --git a/Mi_Lua/luci/view/firewall/cbi_addsnat.htm b/Mi_Lua/luci/view/firewall/cbi_addsnat.htm new file mode 100644 index 0000000..4e1681c --- /dev/null +++ b/Mi_Lua/luci/view/firewall/cbi_addsnat.htm @@ -0,0 +1,66 @@ +<% + local fw = require "luci.model.firewall".init() + local nw = require "luci.model.network".init() + local wz = fw:get_zone("wan") + local lz = fw:get_zone("lan") +%> + +
+ <% if wz and lz then %> +
+ + + + + + + + + + + + + + + + + + + + +
<%:New source NAT%>:
<%:Name%><%:Source zone%><%:Destination zone%><%:To source IP%><%:To source port%>
+ + + + + + + + + + + +
+ + + <% else %> + + <% end %> +
diff --git a/Mi_Lua/luci/view/footer.htm b/Mi_Lua/luci/view/footer.htm new file mode 100644 index 0000000..d2c2a2e --- /dev/null +++ b/Mi_Lua/luci/view/footer.htm @@ -0,0 +1,15 @@ +<%# +LuCI - Lua Configuration Interface +Copyright 2008 Steven Barth +Copyright 2008 Jo-Philipp Wich + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +$Id: footer.htm 3552 2008-10-10 14:37:53Z Cyrus $ + +-%> +<% include("themes/" .. theme .. "/footer") %> \ No newline at end of file diff --git a/Mi_Lua/luci/view/header.htm b/Mi_Lua/luci/view/header.htm new file mode 100644 index 0000000..36bee1b --- /dev/null +++ b/Mi_Lua/luci/view/header.htm @@ -0,0 +1,21 @@ +<%# +LuCI - Lua Configuration Interface +Copyright 2008 Steven Barth +Copyright 2008 Jo-Philipp Wich + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +$Id: header.htm 4234 2009-02-09 13:17:26Z jow $ + +-%> + +<% + if not luci.dispatcher.context.template_header_sent then + include("themes/" .. theme .. "/header") + luci.dispatcher.context.template_header_sent = true + end +%> diff --git a/Mi_Lua/luci/view/index.htm b/Mi_Lua/luci/view/index.htm new file mode 100644 index 0000000..2ed60b2 --- /dev/null +++ b/Mi_Lua/luci/view/index.htm @@ -0,0 +1,22 @@ +<% + local home = luci.dispatcher.build_url("web", "home") +%> + + + + + + + + +小米路由器 + + + + + + \ No newline at end of file diff --git a/Mi_Lua/luci/view/indexer.htm b/Mi_Lua/luci/view/indexer.htm new file mode 100644 index 0000000..ded34d2 --- /dev/null +++ b/Mi_Lua/luci/view/indexer.htm @@ -0,0 +1,15 @@ +<%# +LuCI - Lua Configuration Interface +Copyright 2008 Steven Barth +Copyright 2008 Jo-Philipp Wich + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +$Id: indexer.htm 3552 2008-10-10 14:37:53Z Cyrus $ + +-%> +<% include("themes/" .. theme .. "/indexer") %> \ No newline at end of file diff --git a/Mi_Lua/luci/view/mobile/home.htm b/Mi_Lua/luci/view/mobile/home.htm new file mode 100644 index 0000000..d0ac6b1 --- /dev/null +++ b/Mi_Lua/luci/view/mobile/home.htm @@ -0,0 +1,23 @@ + +<% + local XQVersion = require("xiaoqiang.XQVersion") + local ver = XQVersion.webVersion + local web = luci.dispatcher.build_url("web", "home") +%> + + + + + + + 小米路由器 + + + +

页面跳转中...

+<%include("mobile/inc/footer")%> + + + diff --git a/Mi_Lua/luci/view/mobile/inc/footer.htm b/Mi_Lua/luci/view/mobile/inc/footer.htm new file mode 100644 index 0000000..874e029 --- /dev/null +++ b/Mi_Lua/luci/view/mobile/inc/footer.htm @@ -0,0 +1,30 @@ + \ No newline at end of file diff --git a/Mi_Lua/luci/view/mobile/inc/header.htm b/Mi_Lua/luci/view/mobile/inc/header.htm new file mode 100644 index 0000000..5bf4d74 --- /dev/null +++ b/Mi_Lua/luci/view/mobile/inc/header.htm @@ -0,0 +1,3 @@ +
+

小米路由器

+
\ No newline at end of file diff --git a/Mi_Lua/luci/view/mobile/init/agreement.htm b/Mi_Lua/luci/view/mobile/init/agreement.htm new file mode 100644 index 0000000..6033a38 --- /dev/null +++ b/Mi_Lua/luci/view/mobile/init/agreement.htm @@ -0,0 +1,26 @@ + +<% +local ver = require("xiaoqiang.XQVersion").webVersion +%> + + + + + + + + 小米路由器 + + + + <%include("mobile/inc/header")%> +
+
+
+ <%include('web/inc/agreement')%> +
+
+
+ <%include("mobile/inc/footer")%> + + diff --git a/Mi_Lua/luci/view/mobile/init/guide.htm b/Mi_Lua/luci/view/mobile/init/guide.htm new file mode 100644 index 0000000..2add08e --- /dev/null +++ b/Mi_Lua/luci/view/mobile/init/guide.htm @@ -0,0 +1,340 @@ + +<% +local ver = require("xiaoqiang.XQVersion").webVersion +local sys = require("xiaoqiang.util.XQSysUtil") +if sys.getInitInfo() then + luci.http.redirect(luci.dispatcher.build_url("web", "home")) +end +local wifiUtil = require("xiaoqiang.util.XQWifiUtil") +local fun = require("xiaoqiang.common.XQFunction") +local wifiInfo = wifiUtil.getAllWifiInfo() +local ssid = "" +if wifiInfo[1] then + ssid = wifiInfo[1]['ssid'] +end + +local remote_addr = luci.http.getenv("REMOTE_ADDR") +local mac = luci.sys.net.ip4mac(remote_addr) +%> + + + + + + + 小米路由器 + + + + <%include("mobile/inc/header")%> +
+ + + + + + + + +
+ <%include("mobile/inc/footer")%> + + + + + + + + \ No newline at end of file diff --git a/Mi_Lua/luci/view/mobile/init/hello.htm b/Mi_Lua/luci/view/mobile/init/hello.htm new file mode 100644 index 0000000..3bac27c --- /dev/null +++ b/Mi_Lua/luci/view/mobile/init/hello.htm @@ -0,0 +1,111 @@ + +<% +local ver = require("xiaoqiang.XQVersion").webVersion +local sys = require("xiaoqiang.util.XQSysUtil") +if sys.getInitInfo() then + luci.template.render("mobile/sysauth") +else + sys.setSysPasswordDefault() +end +%> + + + + + + + + 小米路由器 + + + + + + + <%include("mobile/inc/footer")%> + + + + + + diff --git a/Mi_Lua/luci/view/mobile/sysauth.htm b/Mi_Lua/luci/view/mobile/sysauth.htm new file mode 100644 index 0000000..75b92b1 --- /dev/null +++ b/Mi_Lua/luci/view/mobile/sysauth.htm @@ -0,0 +1,51 @@ + +<% + local XQVersion = require("xiaoqiang.XQVersion") + local ver = XQVersion.webVersion + local sys = require("xiaoqiang.util.XQSysUtil") + local binded = sys.getInitInfo() and 'true' or 'false' + local XQLanWanUtil = require("xiaoqiang.util.XQLanWanUtil") + local androidClient = XQVersion.androidClientRouter +%> + + + + + + + 小米路由器 + + + + <%include("mobile/inc/header")%> + + <%include("mobile/inc/footer")%> + + + + \ No newline at end of file diff --git a/Mi_Lua/luci/view/themes/openwrt.org/footer.htm b/Mi_Lua/luci/view/themes/openwrt.org/footer.htm new file mode 100644 index 0000000..32355f6 --- /dev/null +++ b/Mi_Lua/luci/view/themes/openwrt.org/footer.htm @@ -0,0 +1,21 @@ +<%# +LuCI - Lua Configuration Interface +Copyright 2008 Steven Barth +Copyright 2008 Jo-Philipp Wich + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +$Id: footer.htm 7364 2011-08-13 09:52:12Z jow $ + +-%> +
+ + + +

Powered by <%= luci.__appname__ .. " (" .. luci.__version__ .. ")" %>

+ + diff --git a/Mi_Lua/luci/view/themes/openwrt.org/header.htm b/Mi_Lua/luci/view/themes/openwrt.org/header.htm new file mode 100644 index 0000000..d069f76 --- /dev/null +++ b/Mi_Lua/luci/view/themes/openwrt.org/header.htm @@ -0,0 +1,186 @@ +<%# +LuCI - Lua Configuration Interface +Copyright 2008 Steven Barth +Copyright 2008-2010 Jo-Philipp Wich + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +$Id: header.htm 9558 2012-12-18 13:58:22Z jow $ + +-%> +<% + local sys = require "luci.sys" + local http = require "luci.http" + local disp = require "luci.dispatcher" + + local hostname = sys.hostname() + local load1, load5, load15 = sys.loadavg() + + local request = disp.context.path + local request2 = disp.context.request + + local category = request[1] + local cattree = category and disp.node(category) + + local leaf = request2[#request2] + + local tree = disp.node() + local node = disp.context.dispatched + + local categories = disp.node_childs(tree) + + local c = tree + local i, r + + -- tag all nodes leading to this page + for i, r in ipairs(request) do + if c.nodes and c.nodes[r] then + c = c.nodes[r] + c._menu_selected = true + end + end + + http.prepare_content("application/xhtml+xml") + + local function nodeurl(prefix, name, query) + local url = controller .. prefix .. name .. "/" + if query then + url = url .. http.build_querystring(query) + end + return pcdata(url) + end + + local function subtree(prefix, node, level) + if not level then + level = 1 + end + + local childs = disp.node_childs(node) + if #childs > 0 then +%> +
+
    + <% + local selected_node + local selected_name + local i, v + + for i, v in ipairs(childs) do + local nnode = node.nodes[v] + if nnode._menu_selected then + selected_node = nnode + selected_name = v + end + %> +
  • + <%=striptags(translate(nnode.title))%> +
  • + <% + end + %> +
+
+<% + if selected_node then + subtree(prefix .. selected_name .. "/", selected_node, level + 1) + end +%> +
+<% + end + end +-%> + + + + + + + + + + + +<% if node and node.css then %> +<% end -%> +<% if css then %> +<% end -%> + +<%=striptags( hostname .. ( (node and node.title) and ' - ' .. translate(node.title) or '')) %> - LuCI + + + + + + + +
+
+ <% if category then subtree("/" .. category .. "/", cattree) end %> +
+ +
+ + + <%- if luci.sys.process.info("uid") == 0 and luci.sys.user.getuser("root") and not luci.sys.user.getpasswd("root") and category ~= "failsafe" then -%> +
+ <%:No password set!%>
+ <%:There is no password set on this router. Please configure a root password to protect the web interface and enable SSH.%>
+ "><%:Go to password configuration...%> +
+ <%- end -%> diff --git a/Mi_Lua/luci/view/themes/xiaoqiang/footer.htm b/Mi_Lua/luci/view/themes/xiaoqiang/footer.htm new file mode 100644 index 0000000..e69de29 diff --git a/Mi_Lua/luci/view/themes/xiaoqiang/header.htm b/Mi_Lua/luci/view/themes/xiaoqiang/header.htm new file mode 100644 index 0000000..84d0121 --- /dev/null +++ b/Mi_Lua/luci/view/themes/xiaoqiang/header.htm @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/Mi_Lua/luci/view/web/application.htm b/Mi_Lua/luci/view/web/application.htm new file mode 100644 index 0000000..7aa15ff --- /dev/null +++ b/Mi_Lua/luci/view/web/application.htm @@ -0,0 +1,27 @@ +<% + local ver = require("xiaoqiang.XQVersion").webVersion +%> +<%include ("web/inc/head")%> +小米路由器 + + + +
+ <%include ("web/inc/header")%> + + + +
+

施工中,敬请期待...

+
+ <%include ("web/inc/footer")%> +
+<%include ("web/inc/g.js")%> +<%include ("web/inc/reboot.js")%> + + \ No newline at end of file diff --git a/Mi_Lua/luci/view/web/gateway.htm b/Mi_Lua/luci/view/web/gateway.htm new file mode 100644 index 0000000..eff5052 --- /dev/null +++ b/Mi_Lua/luci/view/web/gateway.htm @@ -0,0 +1,130 @@ + + + + + + + + + \ No newline at end of file diff --git a/Mi_Lua/luci/view/web/gateway.html b/Mi_Lua/luci/view/web/gateway.html new file mode 100644 index 0000000..0f5a4e7 --- /dev/null +++ b/Mi_Lua/luci/view/web/gateway.html @@ -0,0 +1,61 @@ + + + + + + + + + \ No newline at end of file diff --git a/Mi_Lua/luci/view/web/inc/agreement.htm b/Mi_Lua/luci/view/web/inc/agreement.htm new file mode 100644 index 0000000..ecad7f2 --- /dev/null +++ b/Mi_Lua/luci/view/web/inc/agreement.htm @@ -0,0 +1,558 @@ +

+ 用户许可使用协议 +

+

+ 【重要提示】 +

+

+ 请您在开始使用小米路由器系列产品(以下简称“本产品”)之前请务必仔细阅读并理解《用户许可使用协议》(以下简称“本协议”)中规定的所有权利和限制。 +

+

+ 小米科技有限责任公司(以下简称“小米”或“我们”)一向尊重并会严格保护用户在使用本产品时的合法权益(包括用户隐私、用户数据等)不受到任何侵犯。 +

+

+ 本产品包括但不限于小米路由器硬件与小米路由器相关软件(包括小米路由器软件操作系统和名为“小米路由”的移动设备端软件应用、PC电脑端软件应用和Mac电脑端软件应用以及小米路由器云服务等产品)及其相关的、均由小米开发的产品。本产品中相关的知识产权(包括本产品的一切版权、专利权、商标权等)均属于小米。 +

+

+ 本协议(包括本文最后部分的隐私政策)是用户(包括通过各种合法途径获取到本产品的自然人、法人或其他组织机构,以下简称“用户”或“您“)与小米之间针对本产品相关事项最终的、完整的且排他的协议,并取代、合并之前的当事人之间关于上述事项的讨论和协议。 +

+

+ 本协议将对用户使用本产品的行为产生法律约束力,您已承诺和保证有权利和能力订立本协议。用户开始使用本产品将视为已经接受本协议,请认真阅读并理解本协议中各条款,包括免除和限制小米责任的免责条款和对用户的权利限制(未成年人审阅时应由法定监护人陪同),如果您不能接受本协议中的全部条款,请勿开始使用本产品。(如果您已经购买我们的硬件产品但无法接受本协议中的全部条款,如需要,硬件产品部分的相关退货政策请查阅http://www.xiaomi.com ) +

+

+ 1、使用 +

+

+ 1.1 帐户 +

+

+ 您无需用户账户即可使用小米路由器。如果您想使用本产品更多功能,您需要注册小米账号并且于注册页面上提供相关的个人信息。您必须承诺和保证: +

+

+ 1.1.1 您提交的所有注册信息必须真实准确; +

+

+ 1.1.2 您使用小米路由器的行为必须合法; +

+

+ 1.1.3 您可以按照小米账号使用说明随时终止使用您的账户。 +

+

+ 本产品将会依据本协议第5条的规定保留或终止您的账户。您必须承诺对您的登录信息保密、不被其他人获取与使用,并且对您在本网站账户下的所有行为负责。您必须将任何有可能触犯法律的、未授权使用或怀疑为未授权使用的行为在第一时间通知本产品。本产品不对您因未能遵守上述要求而造成的损失承担法律责任。 +

+

+ 1.2 网络加速与故障检测服务 +

+

+ 在您使用本产品的过程中,有部分使用场景本产品会在征询您的同意之后对您在使用过程中的一部分网络访问行为进行检测。此举旨在帮助您进行网络加速与并在必要的时候执行故障检测服务。该检测全部由软件程序完成,不会有任何人为监控,只是使用相关的软件程序进行匹配并指引您打开相应的第三方内容或页面。我们承诺将全力确保您在使用本产品过程中的隐私不会被泄露。 +

+

+ 2、终端用户协议 +

+

+ 2.1 许可 +

+

+ 依据本协议规定,本产品将授予您以下不可转让的、非排他的许可: +

+

+ 2.1.1 使用本产品的权利; +

+

+ 2.1.2 在您所有的网络通信过程中下载、安装、使用本产品的权利。 +

+

+ 2.2 限制性条款 +

+

+ 本协议对您的授权将受到以下限制: +

+

+ 2.2.1 您不得对本产品进行任何形式的许可、出售、租赁、转让、发行或其他商业用途; +

+

+ 2.2.2 除非法律禁止此类限制,否则您不得对本产品的任何部分或衍生产品进行修改、翻译、改编、合并、利用、分解、改造或反向编译等; +

+

+ 2.2.3 您不得以创建相同或竞争服务为目的使用本产品; +

+

+ 2.2.4 除非法律明文规定,否则您不得对本产品的任何部分以任何形式或方法进行生产、复制、发行、出售、下载或显示等; +

+

+ 2.2.5 您不得删除或破坏包含在本产品中的任何版权声明或其他所有权标记。 +

+

+ 2.3 权利归属 +

+

+ 2.3.1 小米许可您使用、下载本产品; +

+

+ 2.3.2 小米(及其“授权人”,如有)拥有本产品的所有相关知识产权。 +

+

+ 2.4 费用 +

+

+ 您必须自行负担购买本产品的费用,个人上网或第三方(包括但不限于电信或移动通讯提供商)收取的通讯费、信息费等相关费用。如涉及电信增值服务,我们建议您与增值服务提供商确认相关费用问题。 +

+

+ 2.5 任何本产品的更新版本或未来版本、更新或者其他变更将受到本协议约束。 +

+

+ 3、用户内容 +

+

+ 3.1 用户内容 +

+

+ 3.1.1 用户内容是指该用户下载、发布或以其他方式使用本产品时产生的所有内容(例如:您的信息、图片、音乐或其他内容)。 +

+

+ 3.1.2 您是您的用户内容唯一的责任人,您将承担因您的用户内容披露而导致的您或任何第三方被识别的风险。 +

+

+ 3.1.3 您已同意您的用户内容受到第4条的限制。 +

+

+ 3.2 反馈 +

+

+ 3.2.1 您对小米提出的建议(或称“反馈”),即视为您向小米转让“反馈”的全部权利并同意小米有权利以任何合理方式使用此反馈及其相关信息。我们将视此类反馈信息为非保密且非专有; +

+

+ 3.2.2 您已同意您不会向小米提供任何您视为保密和专有的信息。 +

+

+ 3.3 我们将保留基于我们的判断检查用户分享内容的权利(而非义务) +

+

+ 无论通知与否,我们有权在任何时间以任何理由删除和移动您的用户分享内容。依据第5条规定,我们有权保留或终止您的账户。 +

+

+ 4、权利限制 +

+

+ 4.1 您已同意通过分享或其他方式使用本产品中的相关服务,在使用过程中,您将承担因下述行为所造成的风险而产生的全部法律责任: +

+

+ 4.1.1 破坏宪法所确定的基本原则的; +

+

+ 4.1.2 危害国家安全、泄露国家秘密、颠覆国家政权、破坏国家统一的; +

+

+ 4.1.3 损害国家荣誉和利益的; +

+

+ 4.1.4 煽动民族仇恨、民族歧视,破坏民族团结的; +

+

+ 4.1.5 破坏国家宗教政策,宣扬邪教和封建迷信的; +

+

+ 4.1.6 散布谣言,扰乱社会秩序,破坏社会稳定的; +

+

+ 4.1.7 散布淫秽、色情、赌博、暴力、凶杀、恐怖或者教唆犯罪的; +

+

+ 4.1.8 侮辱或者诽谤他人,侵害他人合法权益的; +

+

+ 4.1.9 含有法律、行政法规禁止的其他内容的。 +

+

+ 4.2 您已同意不在本产品从事下列行为: +

+

+ 4.2.1 发布或分享电脑病毒、蠕虫、恶意代码、故意破坏或改变计算机系统或数据的软件; +

+

+ 4.2.2 未授权的情况下,收集其他用户的信息或数据,例如电子邮箱地址等; +

+

+ 4.2.3 用自动化的方式恶意使用本产品,给服务器造成过度的负担或以其他方式干扰或损害网站服务器和网络链接; +

+

+ 4.2.4 在未授权的情况下,尝试访问本产品的服务器数据或通信数据; +

+

+ 4.2.5 干扰、破坏本产品其他用户的使用。 +

+

+ 5、修改和终止 +

+

+ 5.1 修改 +

+

+ 5.1.1 本协议容许变更。如果本协议有任何实质性变更,我们将通过您的电子邮件或本产品的公告来通知您。变更通知之后,继续使用本产品则视为您已知晓此类变更并同意受条款约束。 +

+

+ 5.1.2 小米保留在任何时候无需通知而修改、保留或关闭本产品任何服务之权利。 +

+

+ 5.1.3 您已同意小米无需因修改、保留或关闭本产品任何服务的行为对您或第三方承担责任。 +

+

+ 5.2 终止 +

+

+ 5.2.1 本协议自您接受之日起生效,在您使用本产品的过程中持续有效,直至依据本协议终止; +

+

+ 5.2.2 尽管有上述规定,如果您使用本产品的时间早于您接受本协议的时间,您在此知晓并同意本协议于您第一次使用本产品时生效,除非依据本协议提前终止; +

+

+ 5.2.3 我们可能会(1)依据法律的规定,保留您使用本产品或者本网站账户的权利;(2)无论是否通知,我们将在任何时间以任何原因终止本协议,包括出于善意的相信您违反了我们可接受使用政策或本协议的其他规定; +

+

+ 5.2.4 不受前款规定所限,如果用户侵犯第三人的版权且小米接到版权所有人或版权所有人的合法代理人的通知后,小米保留终止本协议的权利; +

+

+ 5.2.5 一旦本协议终止,您使用本产品的权利即告终止。您应当知晓您的产品终止意味着您的用户内容将从我们的活动数据库中删除。小米不因终止本协议对您承担任何责任,包括终止您的用户账户和删除您的用户内容。 +

+

+ 6、第三方 +

+

+ 6.1 您已知晓或同意我们的部分服务是基于第三方的技术支持获得。 +

+

+ 例如苹果 iPhone 、安卓系统等。您已知晓本协议是在您与小米之间签订,而非您与上述第三方之间签订。小米是基于本产品所产生的内容、维护、支持服务、保证和由此产生的诉讼等事项的唯一责任人。您已同意遵守且授权给本产品限制您有条件地使用本产品的服务。 +

+

+ 6.2 第三方信息和服务 +

+

+ 6.2.1 本产品包含了第三方的部分信息和服务。小米不控制且不对第三方的信息和服务负责。 +

+

+ 6.2.2 小米仅为您使用方便之目的或为承诺和保证第三方之需要而提供此类信息和服务。 +

+

+ 6.2.3 您需对您使用第三方信息和服务产生的风险承担法律责任。 +

+

+ 6.2.4 当您访问第三方信息和服务时,适用第三方的条款和政策。 +

+

+ 6.3 其他用户 +

+

+ 6.3.1 本产品包含其他用户提供的用户内容。本产品不控制且不对用户内容承担法律责任。本产品没有检查、监控、审批、核准、承诺并保证用户内容的义务。您必须因使用用户内容和与其他用户互动产生的风险承担法律责任。 +

+

+ 6.3.2 您与其他用户的互动仅属于您与其他用户之间的行为,本产品对此类行为不承担任何法律责任。您已同意本产品对此类互动行为的结果不承担法律责任。 +

+

+ 7、赔偿 +

+

+ 7.1 您已同意无害地使用本产品,避免小米因下述行为或相关行为遭受来自第三方的任何投诉、诉讼、损失、损害、责任、成本和费用(包括律师费): +

+

+ 7.1.1 您使用本产品的行为; +

+

+ 7.1.2 您的用户内容; +

+

+ 7.1.3 您违反本协议的行为。 +

+

+ 7.2 小米保留专属抗辩权和请求赔偿的权利。 +

+

+ 7.3 您已同意,除非获得小米书面同意,您不得在您与小米共同对第三方提起的诉讼中单方和解。 +

+

+ 7.4 小米将尽合理努力将此类诉讼、诉讼行为或进程通知您。 +

+

+ 7.5 在任何情况下,本产品都不对您或任何第三方因本协议产生的任何间接性、后果性、惩戒性的、偶然的、特殊或惩罚性的损害赔偿承担责任。访问、使用本产品所产生的损坏计算机系统或移动通讯设备数据库的风险将由您个人承担。 +

+

+ 8、免责声明 +

+

+ 8.1 如发生下述情形,本产品不承担任何法律责任: +

+

+ 8.1.1 依据法律规定或相关政府部门的要求提供您的个人信息; +

+

+ 8.1.2 由于您的使用不当而导致任何个人信息的泄露; +

+

+ 8.1.3 任何由于黑客攻击,电脑病毒侵入,非法内容信息,骚扰信息的屏蔽,政府管制以及其他任何网络、技术、通信线路、信息安全管理措施等原因造成的服务中断、受阻等不能满足用户要求的情形; +

+

+ 8.1.4 用户因第三方如运营商的通讯线路故障、技术问题、网络、电脑故障、系统不稳定及其他因不可抗力造成的损失的情形; +

+

+ 8.1.5 使用本产品可能存在的来自他人匿名或冒名的含有威胁、诽谤、令人反感或非法内容的信息而招致的风险; +

+

+ 8.1.6 本产品明文声明,不以明示或默示及以任何形式对本产品及其合作公司服务之及时性、安全性、准确性做出担保。 +

+

+ 9、隐私政策 +

+

+ “小米路由器系列产品隐私政策”为本协议的一部分,您可以在本文的最后部分找到。请您认真阅读并理解隐私协议中的所有内容。 +

+

+ 10、通知 +

+

+ 10.1 用户必须在小米账号中提供您最近经常使用的有效的电子邮件地址。您所提供的电子邮件地址无法使用或者因任何原因我们无法将通知送达给您而产生的风险,本产品不承担责任。 +

+

+ 10.2 本产品发布的公告通知及向您所发送的包含此类通知的电子邮件毫无疑问构成有效通知。 +

+

+ 11、适用法律 +

+

+ 11.1 本协议适用中华人民共和国法律。 +

+

+ 11.2 如果双方发生纠纷,应本着友好的原则协商解决;如协商不成,应向小米所在地的法院提起诉讼。 +

+

+ 12、独立性 +

+

+ 本协议中的某些条款因故无法适用,则本协议的其他条款继续适用且无法适用的条款将会被修改,以便其能够依法适用。 +

+

+ 13、完整性 +

+

+ 13.1 本协议(包括隐私政策)是您和本产品之间关于本产品相关事项的最终的、完整的、排他的协议,且取代和合并之前当事人关于此类事项(包括之前的最终用户许可、服务条款和隐私政策)的讨论和协议。 +

+

+ 13.2 每部分的题目只为阅读之便而无任何法律或合同义务。 +

+

+ 13.3 除非小米书面同意,您不得转让本协议所规定的权利义务。任何违反上述规定企图转让的行为均无效。 +

+

+
+

+

+ 隐私政策 +

+

+ 您可以通过多种不同的方式使用我们的服务,例如登录小米官网购买小米路由器硬件产品、在不同的终端上使用小米路由软件产品、服务等。如果您与我们分享信息(例如,通过创建一个小米帐户),我们就能为您提供更好的服务,如显示相关程度更高的产品更新和广告、帮助您与他人联系或者更轻松快捷地与他人分享内容等。基于此目的,我们将向您解释我们对信息的收集和使用方式,以及您可采用什么方式来保护自己的隐私权。 +

+

+ 我们的隐私政策包括了以下几个方面的问题: +

+

+ • 我们收集哪些信息。 +

+

+ • 我们如何收集和使用信息。 +

+

+ • 您如何选择性提供这些信息,以及如何访问和更新这些信息。 +

+

+ • 信息的分享、安全以及隐私政策的适用范围和修订。 +

+

+ 1、我们收集的信息 +

+

+ 我们收集您的两种信息:个人信息(个人信息是可用于唯一地识别或联系某人的数据)和非个人信息(即不会与任何特定个人直接相关联的数据)。如果我们将非个人信息与个人信息合并在一起,在保持合并的期间内,此类信息将被视为个人信息。 +

+

+ 我们收集您的两种信息:个人信息(个人信息是可用于唯一地识别或联系某人的数据)和非个人信息(即不会与任何特定个人直接相关联的数据)。如果我们将非个人信息与个人信息合并在一起,在保持合并的期间内,此类信息将被视为个人信息。 +

+

+ 2、我们如何收集和利用的信息 +

+

+ 您与北京小米科技有限责任公司(以下简称“小米”或“我们”)及其关联公司进行互动时,您可能会被要求提供您同意使用我们基本产品服务所必需的某些个人信息。该个人信息可能与其他信息合并在一起,被用于改进我们的产品或服务等。下文是小米可能收集的个人信息的类型以及我们如何使用该信息的一些示例。 +

+

+ 2.1 个人信息的收集 +

+

+ • 当您创建小米账户、下载及更新小米软件、在小米手机在线零售店注册、参加在线调查或者参加其他与小米公司的互动时,我们会要求您提供个人信息,包括但不限于您的姓名、电话号码、电子邮件地址、邮寄地址等。 +

+

+ • 当您购买小米产品和服务时,我们会收集有关个人信息,包括但不限于:交货详情、银行帐号、信用卡详情、账单地址、信用核查以及其他财务信息、联系及交流的记录等。 +

+

+ • 当您使用小米产品与家人和朋友分享您的内容、发送信息和产品或者在小米论坛上邀请其他人时,小米会收集您提供的与上述人士有关的个人信息,如姓名、邮寄地址、电子邮件地址及电话号码等 +

+

+ • 当您首次使用并激活小米移动设备时,您的移动用户识别信息、移动设备唯一识别码以及您设备的大概地理位置信息将被发送给小米。上述信息的收集也可能适用于您更新系统或软件、恢复出厂设置等情况。 +

+

+ 2.2 个人信息的使用 +

+

+ • 小米将严格遵守本隐私政策及其更新所载明的内容来使用您的个人信息。您的个人信息将仅用于收集时即已确定并且经过您同意的目的,如有除此之外的任何其他用途,我们都会提前征得您的同意。 +

+

+ • 我们收集的个人信息将被用于向您提供小米的产品和服务、处理您的订单或履行您与小米之间的合同,以确保我们产品和服务的功能和安全、验证您的身份、防止并追究欺诈或其他不当使用的情形。 +

+

+ • 我们收集的个人信息将被用于我们的产品和服务开发,尽管一般情况下,我们为此目的仅使用综合信息和统计性信息。 +

+

+ • 我们收集的个人信息将被用于与您进行交流,例如,在小米产品或服务更新、发布的第一时间向您发出通知。 +

+

+ • 我们所收集的个人信息将被用于进行产品的个性化设计,并向您提供更为贴身的服务,例如:在我们的服务中进行推荐、展示专为您打造的内容和广告或者用于调研。 +

+

+ • 如果您参与小米举办的抽奖、竞赛或类似推广活动,我们会将您提供的个人信息用于管理此类活动。 +

+

+ • 您的移动用户识别信息、移动设备唯一识别码和地理位置信息可被用来激活您的保修服务和特定的软件许可,并可能邀请您参加调查。我们也会使用此等信息用于改善我们的产品和分析我们业务运营的效率。但我们不会用此等信息来追踪您的位置情况。 +

+

+ 2.3 非个人信息的使用 +

+

+ • 当您创建小米账户、下载小米软件及其更新、在小米手机在线零售店注册、参加在线调查或者参加其他与小米公司的互动时,我们会收集您诸如职业、语言、邮编、区号以及使用小米产品所在的时区等信息。 +

+

+ • 使用习惯和发现问题相关的信息——当您在使用小米路由器的部分应用时,我们会根据需要,匿名统计一部分您对网络使用的情况从而为您提供更好的上网体验。此外,当小米路由器系统发生非正常状态或崩溃时,我们将可能会收集一些便于诊断问题的环境信息如设备ID、互联网协议地址、路由数据包等。 +

+

+ • Cookies和其他技术收集的信息——小米的网站、在线服务、互动应用软件、电子邮件消息和广告宣传可能会使用cookies以及如像素标签和网站信标的其他技术来收集和存储您的非个人信息。 +

+

+ • 日志信息——当您访问小米服务器时,我们的服务器会自动记录某些日志信息。这类服务器日志信息可能包含如下信息:IP 地址、浏览器类型、浏览器语言、refer来源页、操作系统时间标记和点击流数据。 +

+

+ • 我们收集的诸如职业、语言、邮编、区号以及使用小米产品所在的时区等非个人信息,可以帮助我们更好地了解用户行为,改进我们的产品、服务和广告宣传。 +

+

+ • 日志信息——当您访问小米服务器时,我们的服务器会自动记录某些日志信息。这类服务器日志信息可能包含如下信息:IP 地址、浏览器类型、浏览器语言、refer来源页、操作系统时间标记和点击流数据。 +

+

+ • 我们可以通过分析小米“用户体验改进计划”获取的统计数据来持续不断地提升产品和服务的操作体验、运行性能、修复问题、改善功能设计等。 +

+

+ • 我们通过使用 cookies 和其他技术(例如像素标签)收集到的信息,为您带来更好的用户体验,并提高我们的总体服务品质。例如,通过保存您的语言偏好设置,我们可以用您的首选语言显示我们的服务。我们可以通过使用像素标签向用户发送可阅读格式的电子邮件,并告知我们邮件是否被打开。我们可将此等信息用于减少向用户发送电子邮件或者不向用户发送电子邮件。 +

+

+ 每个人对于隐私权的关注各有侧重,基于此,我们将明确指出我们收集信息的方式,以便您选择接受信息的提供方式。 +

+

+ • 您可以自由设定是否加入“用户体验改进计划”,即可以通过关闭“用户体验改进计划”项的开关从而退出该计划。 +

+

+ • 您可以从设备设置上修改您设备的定位设置,例如:变更或禁用定位方法或定位服务器,或修改您位置信息的准确性,从而改变向小米提供的位置信息。 +

+

+ • 您可以根据自己的需要来修改浏览器的设置以拒绝或接受 cookies,也可以随时从您存储设备中将其删除,或者在写入cookies时通知您。 +

+

+ 3、个人信息的访问和更新 +

+

+ 通过点击[https://account.xiaomi.com]来登录你的账户,您能够访问并更新自己的个人信息,您也可以删除部分个人信息或者要求我们删除您的个人账户,除非我们出于法律方面的原因而必须保留这些信息。 +

+

+ 无论您在何时使用我们的服务,我们都力求让您能够访问自己的个人信息。如果这些信息有误,我们会努力通过各种方式让您快速更新信息或删除信息(除非我们出于合法业务或法律方面的原因而必须保留这些信息)。在更新您的个人信息时,我们可能会要求您先验证自己的身份,然后再处理您的请求。 +

+

+ 对于那些无端重复、需要过多技术投入、对他人隐私权造成风险或者非常不切实际的请求,我们可能予以拒绝。 +

+

+ 只要我们能够让您访问和修改信息,而且其不需要过多投入,则我们会免费提供。我们力求对服务进行完善的维护,以保护信息免遭意外或恶意的破坏。因此,当您从我们的服务中删除信息后,我们可能不会立即从在用的 +

+

+ 服务器中删除这些信息的残留副本,也可能不会从备份系统中删除相应的信息。 +

+

+ 4、和第三方分享信息 +

+

+ 除非本隐私政策载明的有限共享,我们会严格保密您的个人信息并不会向第三方共享您的个人信息。 +

+

+ 您于此授权,以下情形我们将会向第三方共享您的个人信息而无需通过您的同意: +

+

+ • 我们因法律调查、诉讼、强制执行或其它法律强制性规定或要求,而同第三方共享这些个人信息。 +

+

+ • 我们将个人信息提供给我们的子公司、关联公司或其他可信赖的企业或个人,以便其代表我们处理个人信息。我们要求上述各方同意按照我们的规定、本隐私权政策以及其他任何适用的保密和安全措施来处理这些个人信息。 +

+

+ • 如果我们发生重组、合并或出售,则我们在将我们收集的一切个人信息进行转让之前,继续按照现行的隐私政策约束来保证其秘密性并会通知所有受到影响的用户。 +

+

+ 我们可能会基于经营或保护用户的需要,如为了展示我们产品或服务的整体使用趋势,同公众以及合作伙伴合理地分享汇总的非个人信息。 +

+

+ 5、信息安全 +

+

+ 我们努力为小米的用户提供保护,以免我们保存的信息在未经授权的情况下被访问、篡改、披露或破坏。为此,我们特别采取了以下措施: +

+

+ 我们使用 SSL 对许多服务进行加密。 +

+

+ 我们会审查信息收集、存储和处理方面的做法(包括物理性安全措施),以避免各种系统遭到未经授权的访问。 +

+

+ 我们只允许那些为了帮我们处理个人信息而需要知晓这些信息的小米员工、授权代为处理服务公司的人员访问个人信息,而且他们需要履行严格的合同保密义务,如果其未能履行这些义务,就可能会被追究法律责任或被终止其与小米的关系。 +

+

+ 对我们来说,您的信息的安全非常重要。因此,我们将不断努力保障您的个人信息安全,并实施存储和传输全程安全加密等保障手段,以免您的信息在未经授权的情况下被访问、使用或披露。同时某些加密数据的具体内容,除了用户自己,其他人都无权访问。 +

+

+ 6、隐私政策的适用范围 +

+

+ 我们的隐私政策不适用于第三方产品和/或服务。小米路由器系列产品和服务中可能含有第三方产品和服务,如:金山快盘。当您使用第三方产品或接收第三方服务时,他们可能获取您的信息,因此,我们在此提醒您注意阅读第三方的隐私政策。(在本协议最后部分有金山快盘产品与服务的相关说明) +

+

+ 同时,本隐私政策不适用于在您点击链接后的外部网站收集数据的行为。 +

+

+ 7、对儿童个人信息的收集和使用 +

+

+ 我们建议13周岁以下的儿童在其法定监护人的许可和指导下使用小米产品或服务。 +

+

+ 小米不会在明知对方为儿童的情况下收集其个人信息,也不会向任何第三方透露其个人信息,监护人有权拒绝小米及其关联公司进一步收集被监护人的个人信息,或要求小米及其关联公司删除该被监护人个人信息。如果我们发现我们收集了年龄不满13周岁的儿童的个人信息,我们将采取措施尽快地删除此等信息。 +

+

+ 8、本隐私政策的修订 +

+

+ 本隐私政策容许调整,未经您明示同意,我们不会削弱您按照本隐私权政策所应享有的权利,除非通过您提供的电子邮件地址向您发送通知或发布网站公告。 +

+

+ 9、特定产品服务的惯例 +

+

+ 以下备注了您可能使用的某些小米路由器相关产品和服务的特定隐私权惯例。 +

+

+ 金山快盘云同步服务 +

+

+ 您在使用小米路由器系列产品过程中会使用到由北京金山软件有限公司的产品“金山快盘”为您提供的“路由器云备份”功能。关于金山快盘的服务条款请查阅:http://www.kuaipan.cn/agreement/ +

+

+ 关于金山快盘: +

+

+ 金山快盘秉承极致、专注、快速的理念,凭借金山多年技术积累,将为金山其他业务提供支撑,同时致力于为用户提供稳定、安全、便捷的个人云存储服务。关于金山快盘的相关信息以及请查看:http://www.kuaipan.cn +

diff --git a/Mi_Lua/luci/view/web/inc/footer.htm b/Mi_Lua/luci/view/web/inc/footer.htm new file mode 100644 index 0000000..4e8843a --- /dev/null +++ b/Mi_Lua/luci/view/web/inc/footer.htm @@ -0,0 +1,18 @@ +<% +local XQSysUtil = require "xiaoqiang.util.XQSysUtil" +local xqlanwanutil = require "xiaoqiang.util.XQLanWanUtil" +local macdefault = string.upper(xqlanwanutil.getDefaultMacAddress()) +local romVersion = XQSysUtil.getRomVersion() +local _romChannel = XQSysUtil.getChannel() +local romChannel = "开发版" +if _romChannel == "current" then + romChannel = "内测版" +end +if _romChannel == "release" then + romChannel = "稳定版" +end +%> +
+

<%:系统版本%>: MiWiFi Rom <%=romVersion%> <%=romChannel%>    <%:MAC地址%>: <%=macdefault%>

+

© 2014 <%:小米路由器%>|官方网站|官方微博|官方微信|用户社区|<%:服务热线%> 400-100-5678

+

\ No newline at end of file diff --git a/Mi_Lua/luci/view/web/inc/footermini.htm b/Mi_Lua/luci/view/web/inc/footermini.htm new file mode 100644 index 0000000..cdcdf8d --- /dev/null +++ b/Mi_Lua/luci/view/web/inc/footermini.htm @@ -0,0 +1,3 @@ +
+

© 2014 <%:小米路由器%>|<%:服务热线%> 400-100-5678

+

\ No newline at end of file diff --git a/Mi_Lua/luci/view/web/inc/g.js.base.htm b/Mi_Lua/luci/view/web/inc/g.js.base.htm new file mode 100644 index 0000000..ee0f730 --- /dev/null +++ b/Mi_Lua/luci/view/web/inc/g.js.base.htm @@ -0,0 +1,231 @@ +<% + local XQVersion = require("xiaoqiang.XQVersion") + local XQSysUtil = require ("xiaoqiang.util.XQSysUtil") + local XQLanWanUtil = require("xiaoqiang.util.XQLanWanUtil") + local ver = XQVersion.webVersion + local monitor = XQLanWanUtil.getWanMonitorStat() + local pcClient = XQVersion.pcClientServer + local macClient = XQVersion.macClientServer + local androidClient = XQVersion.androidClientServer + if monitor.WANLINKSTAT ~= "UP" then + pcClient = XQVersion.pcClientRouter + macClient = XQVersion.macClientRouter + androidClient = XQVersion.androidClientRouter + end + + local remote_addr = luci.http.getenv("REMOTE_ADDR") + local mac = luci.sys.net.ip4mac(remote_addr) + + -- URL ENV SWTICH + local XQConfigs = require "xiaoqiang.common.XQConfigs" + local pspBaseUrl + local xmServerUrl + if XQConfigs.SERVER_CONFIG == 0 then + pspBaseUrl = XQConfigs.PASSPORT_CONFIG_ONLINE_URL + xmServerUrl = XQConfigs.XQ_SERVER_ONLINE_API_URL + elseif XQConfigs.SERVER_CONFIG == 1 then + pspBaseUrl = XQConfigs.PASSPORT_CONFIG_PREVIEW_URL + xmServerUrl = XQConfigs.XQ_SERVER_STAGING_API_URL + end +%> + + + + + + + + + + + + diff --git a/Mi_Lua/luci/view/web/inc/g.js.htm b/Mi_Lua/luci/view/web/inc/g.js.htm new file mode 100644 index 0000000..d5a5fac --- /dev/null +++ b/Mi_Lua/luci/view/web/inc/g.js.htm @@ -0,0 +1,161 @@ +<% + local XQVersion = require("xiaoqiang.XQVersion") + local XQSysUtil = require ("xiaoqiang.util.XQSysUtil") + local XQLanWanUtil = require("xiaoqiang.util.XQLanWanUtil") + local router_name = XQSysUtil.getRouterName() or "小米路由器" + local isBinded = (XQSysUtil.getPassportBindInfo() and 'true') or 'false' +%> + + diff --git a/Mi_Lua/luci/view/web/inc/head.htm b/Mi_Lua/luci/view/web/inc/head.htm new file mode 100644 index 0000000..4408891 --- /dev/null +++ b/Mi_Lua/luci/view/web/inc/head.htm @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file diff --git a/Mi_Lua/luci/view/web/inc/header.htm b/Mi_Lua/luci/view/web/inc/header.htm new file mode 100644 index 0000000..db7536d --- /dev/null +++ b/Mi_Lua/luci/view/web/inc/header.htm @@ -0,0 +1,20 @@ +<% + local XQSysUtil = require "xiaoqiang.util.XQSysUtil" + local homeUrl = '/' + if XQSysUtil.getInitInfo() then + homeUrl = luci.dispatcher.build_url("web", "home") + end +%> + +
+
+

小米路由器

+
+ +
+
+
diff --git a/Mi_Lua/luci/view/web/inc/nav.htm b/Mi_Lua/luci/view/web/inc/nav.htm new file mode 100644 index 0000000..18f9039 --- /dev/null +++ b/Mi_Lua/luci/view/web/inc/nav.htm @@ -0,0 +1,16 @@ + \ No newline at end of file diff --git a/Mi_Lua/luci/view/web/inc/reboot.js.htm b/Mi_Lua/luci/view/web/inc/reboot.js.htm new file mode 100644 index 0000000..be43c54 --- /dev/null +++ b/Mi_Lua/luci/view/web/inc/reboot.js.htm @@ -0,0 +1,119 @@ +<% + local lanIp = require("xiaoqiang.XQVersion").webDefaultHost +%> + diff --git a/Mi_Lua/luci/view/web/inc/upgrade.js.htm b/Mi_Lua/luci/view/web/inc/upgrade.js.htm new file mode 100644 index 0000000..7b1bda2 --- /dev/null +++ b/Mi_Lua/luci/view/web/inc/upgrade.js.htm @@ -0,0 +1,159 @@ +//check offline +global_event.checkOffline = false; +$(global_event).on('upgrade:isoffline', function(evt, data){ + global_event.checkOffline = true; + var imgUrl = 'http://'+ location.host +'/xiaoqiang/web/img/logo.png', + timer = null, + loadImg = function(){ + console.log(global_event.offline); + var img = new Image(); + img.onload = function(){ + if (global_event.offline) { + return; + } + window.clearTimeout(timer); + timer = window.setTimeout(function(){ + loadImg(); + }, 1500); + }; + img.onerror = function(){ + global_event.offline = true; + }; + img.src = imgUrl+'?' + (+new Date()); + }; + global_event.offline = false; + loadImg(); +}); +//flash ROM V2 +$(global_event).on('upgrade:downAndInstall', function(evt, data){ + $.getJSON('<%=luci.dispatcher.build_url("api", "xqsystem", "upgrade_rom")%>', {}, function(rsp){ + if (rsp.code == 0) { + //check download percent + $(global_event).trigger('upgrade:downloadPercent'); + //check shell status + $(global_event).trigger('upgread:flashCheck'); + }else{ + window.top.art.dialog({ + width: 400, + title: '升级失败', + content: rsp.msg + }).lock().time(3 * 1000); + setTimeout('self.location.reload(1)', 3 * 1000); + } + }); +}); +//check download file percent +$(global_event).on('upgrade:downloadPercent', function(evt, data){ + $('#dldProgress').show(); + $('#btnUpgread').hide(); +}); + +//check flash status +$(global_event).on('upgread:flashCheck', function(evt, data){ + var timer = window.setInterval(function(){ + $.ajax({ + url: '<%=luci.dispatcher.build_url("api", "xqsystem","upgrade_status")%>', + type: 'GET', + dataType: 'json', + cache: false + }) + .done(function(rsp){ + if (rsp.code == 0) { + console.log('ajax done',global_event.offline); + var status = rsp.status; + // 无需升级 + if (status == 0) { + window.clearInterval(timer); + $(global_event).trigger('upgrade:flashdone'); + } + if (status == 3) { + $("#btnCancel").show(); + } else { + $("#btnCancel").hide(); + } + if (/(3|5|11|12)/.test(status) && rsp.percent) { + $("#persent").html(rsp.percent); + $("#persentWidth").css("width", rsp.percent+"%"); + } + //flash fail + if(/(6|7|8|9|10)/.test(status)){ + window.clearInterval(timer); + $(global_event).trigger('upgrade:flashfail', {status: status}); + } + // flash rom ing + if (/(5|12)/.test(status)) { + if (!global_event.checkOffline) { + $(global_event).trigger('upgrade:flashstart'); + $(global_event).trigger('upgrade:isoffline'); + } + } + // flash success + if (status == 11) { + window.clearInterval(timer); + $(global_event).trigger('upgrade:flashdone'); + } + } + }) + .fail(function() { + // 和服务器断开连接 + console.log('ajax fail', global_event.offline); + if ( global_event.checkOffline ) { + if (global_event.offline) { + window.clearInterval(timer); + $(global_event).trigger('upgrade:flashdone'); + } + } else { + $(global_event).trigger('upgrade:isoffline'); + } + }); + }, 1500); +}); +//flash start +$(global_event).on('upgrade:flashstart', function(evt, data){ + if (global_event.wait) { + return; + } + global_event.wait = window.top.art.dialog({ + width:400, + cancel: false, + title:'固件安装中', + content :'正在安装固件, 请不要切断路由器电源' + }).lock(); +}); +//flash fail +$(global_event).on('upgrade:flashfail', function(evt, data){ + if (global_event.wait) { + global_event.wait.close(); + } + var msg = { + 6: '下载失败', + 7: '没有磁盘空间', + 8: '下载失败', + 9: '升级包校验失败', + 10: '刷写失败' + }; + var status = data.status; + window.top.art.dialog({ + width: 400, + title:'固件安装中', + content: msg[status] + ',固件安装失败,请刷新重试' + }).lock().time(2 * 1000); + window.setTimeout('self.location.reload(1)', 2 * 1000); +}); +//flash done +$(global_event).on('upgrade:flashdone', function(evt, data){ + if (global_event.wait) { + global_event.wait.close(); + } + $(global_event).trigger('reboot:wait', { + action : '升级路由器固件', + refresh : true + }); +}); +//绑定事件 +$(global_event).on ('upgrade:initEvent', function(evt, data){ + $('#btnUpgread').on('click', function(e){ + e.preventDefault(); + $(global_event).trigger('upgrade:downAndInstall'); + }); +}); diff --git a/Mi_Lua/luci/view/web/index.htm b/Mi_Lua/luci/view/web/index.htm new file mode 100644 index 0000000..c623964 --- /dev/null +++ b/Mi_Lua/luci/view/web/index.htm @@ -0,0 +1,412 @@ +<% + local ver = require("xiaoqiang.XQVersion").webVersion + local XQSysUtil = require("xiaoqiang.util.XQSysUtil") + local XQLanWanUtil = require("xiaoqiang.util.XQLanWanUtil") + local hardwareVersion = XQSysUtil.getHardware() + local romVersion = XQSysUtil.getRomVersion() + local sysInfo = XQSysUtil.getSysInfo() + local router_name = XQSysUtil.getRouterName() or "我" + local sysCore = (sysInfo.core == 2 and '双核') or '单核' + local sysCpu = sysCore .." ".. sysInfo.hz + local sysDisk = XQSysUtil.getDiskSpace() + local remote_addr = luci.http.getenv("REMOTE_ADDR") + --local mac = string.upper(luci.sys.net.ip4mac(remote_addr)) + local mac = XQLanWanUtil.getLanWanInfo("wan").mac +%> +<%include ("web/inc/head")%> +路由器状态 - 小米路由器 + + + + +<%include ("web/inc/g.js.base")%> + + + + +<%include ("web/inc/g.js")%> + + + \ No newline at end of file diff --git a/Mi_Lua/luci/view/web/init/agreement.htm b/Mi_Lua/luci/view/web/init/agreement.htm new file mode 100644 index 0000000..6009bfb --- /dev/null +++ b/Mi_Lua/luci/view/web/init/agreement.htm @@ -0,0 +1,27 @@ +<% +local ver = require("xiaoqiang.XQVersion").webVersion +local sys = require("xiaoqiang.util.XQSysUtil") +%> +<%include ("web/inc/head")%> +初始化引导 - 小米路由器 + + + +
+ <%include ("web/inc/header")%> +
+
+
+

小米路由器 用户使用协议

+
+
+
+ <%include('web/inc/agreement')%> +
+
+
+
+ <%include ("web/inc/footer")%> +
+ + diff --git a/Mi_Lua/luci/view/web/init/guide.htm b/Mi_Lua/luci/view/web/init/guide.htm new file mode 100644 index 0000000..0a97ca6 --- /dev/null +++ b/Mi_Lua/luci/view/web/init/guide.htm @@ -0,0 +1,442 @@ +<% +local ver = require("xiaoqiang.XQVersion").webVersion +local wifiUtil = require("xiaoqiang.util.XQWifiUtil") +local fun = require("xiaoqiang.common.XQFunction") +local XQLanWanUtil = require("xiaoqiang.util.XQLanWanUtil") +local sys = require("xiaoqiang.util.XQSysUtil") +if sys.getInitInfo() then + luci.http.redirect(luci.dispatcher.build_url("web", "home")) +end +local wifiInfo = wifiUtil.getAllWifiInfo() +local ssid = "" +local wanType = 2 +local AutoWanType = XQLanWanUtil.getAutoWanType() +if AutoWanType then + wanType = AutoWanType +end +if wifiInfo[1] then + ssid = wifiInfo[1]['ssid'] +end +local remote_addr = luci.http.getenv("REMOTE_ADDR") +local mac = luci.sys.net.ip4mac(remote_addr) +local lanType = wifiUtil.getDeviceWifiIndex(mac) +%> +<%include ("web/inc/head")%> +初始化引导 - 小米路由器 + + + +
+ <%include ("web/inc/header")%> +
+
+
+ + + + + + +
+
+ + +
+ + + + + + + + + +
+
+
+
+ <%include ("web/inc/footermini")%> +
+<%include ("web/inc/g.js.base")%> + + + diff --git a/Mi_Lua/luci/view/web/init/hello.htm b/Mi_Lua/luci/view/web/init/hello.htm new file mode 100644 index 0000000..f2ede4a --- /dev/null +++ b/Mi_Lua/luci/view/web/init/hello.htm @@ -0,0 +1,80 @@ +<% +local ver = require("xiaoqiang.XQVersion").webVersion +local sys = require("xiaoqiang.util.XQSysUtil") +if sys.getInitInfo() then + luci.http.redirect(luci.dispatcher.build_url("web", "home")) +else + sys.setSysPasswordDefault() +end +%> +<%include ("web/inc/head")%> +初始化引导 - 小米路由器 + + + + +
+ <%include ("web/inc/header")%> + + <%include ("web/inc/footermini")%> +
+ +<%include ("web/inc/g.js.base")%> + + + diff --git a/Mi_Lua/luci/view/web/init/webinitrdr.htm b/Mi_Lua/luci/view/web/init/webinitrdr.htm new file mode 100644 index 0000000..1ca5b06 --- /dev/null +++ b/Mi_Lua/luci/view/web/init/webinitrdr.htm @@ -0,0 +1,204 @@ + + + + + + + + + +小米路由器 + + + + +
+
+ +
+
+
+

你连接的小米路由器还未初始化

+

请稍候,会自动为你跳转到引导页面...

+

loading

+

如果未能跳转,请直接访问miwifi.com

+

+
+
+

Copyright © 2013 xiaomi.com

+ 小米,为发烧友而生! +
+
+ + + \ No newline at end of file diff --git a/Mi_Lua/luci/view/web/manager.htm b/Mi_Lua/luci/view/web/manager.htm new file mode 100644 index 0000000..d9f6df8 --- /dev/null +++ b/Mi_Lua/luci/view/web/manager.htm @@ -0,0 +1,425 @@ +<% + local ver = require("xiaoqiang.XQVersion").webVersion + local remote_addr = luci.http.getenv("REMOTE_ADDR") + local mac = luci.sys.net.ip4mac(remote_addr) +%> +<%include ("web/inc/head")%> +终端管理 - 小米路由器 + + + + + + +<%include ("web/inc/g.js.base")%> +<%include ("web/inc/g.js")%> + + + diff --git a/Mi_Lua/luci/view/web/netdetection.htm b/Mi_Lua/luci/view/web/netdetection.htm new file mode 100644 index 0000000..c1c17d0 --- /dev/null +++ b/Mi_Lua/luci/view/web/netdetection.htm @@ -0,0 +1,251 @@ +<% + local ver = require("xiaoqiang.XQVersion").webVersion +%> +<%include ("web/inc/head")%> +网络检测 - 小米路由器 + + + +
+
+
+

诊断完成: 网络很差

+

+
+
+
+
+
+ + 网络连接状态 + 正在检测中... +
+ +
+
+
+ + 外网连接状态 + 正在检测中... +
+ +
+
+
+ + 路由硬件状态 + 正在检测中... +
+ +
+
+
+ + 连接速度状态 + 正在检测中... +
+ +
+ +
+ + +
+<%include ("web/inc/g.js.base")%> + \ No newline at end of file diff --git a/Mi_Lua/luci/view/web/netset.htm b/Mi_Lua/luci/view/web/netset.htm new file mode 100644 index 0000000..303ed1c --- /dev/null +++ b/Mi_Lua/luci/view/web/netset.htm @@ -0,0 +1,210 @@ +<% + local ver = require("xiaoqiang.XQVersion").webVersion +%> +<%include ("web/inc/head")%> +路由设置 - 小米路由器 + + + + +<%include ("web/inc/g.js.base")%> +<%include ("web/inc/g.js")%> + + + \ No newline at end of file diff --git a/Mi_Lua/luci/view/web/plugin.htm b/Mi_Lua/luci/view/web/plugin.htm new file mode 100644 index 0000000..bbe62ad --- /dev/null +++ b/Mi_Lua/luci/view/web/plugin.htm @@ -0,0 +1,661 @@ +<% + local ver = require("xiaoqiang.XQVersion").webVersion + local cryptoUtil = require("xiaoqiang.util.XQCryptoUtil") + local XQSysUtil = require ("xiaoqiang.util.XQSysUtil") + local http = require ("luci.http") + local secretKey = "Ep9]B28z3!3D66*3CF_18663" + local routerName = XQSysUtil.getRouterName() or "miwifi" + local XQConfigs = require("xiaoqiang.common.XQConfigs") + local cmd = XQConfigs.THRIFT_TO_MQTT_GET_DEVICEID + local LuciUtil = require("luci.util") + local deviceId = LuciUtil.exec(cmd) + local from = "xiaomi_router" + local sign = cryptoUtil.md5Str(deviceId .. from .. routerName .. secretKey) + local xunleiurl = string.format("http://dynamic.i.xunlei.com/device_binding/entrance.php?deviceId=%s&from=%s&name=%s&sign=%s", http.urlencode(deviceId), http.urlencode(from), http.urlencode(routerName), http.urlencode(sign)) +%> +<%include ("web/inc/head")%> +应用管理 - 小米路由器 + + + + +
+ + + +<%include ("web/inc/g.js.base")%> + +<%include ("web/inc/g.js")%> + + + \ No newline at end of file diff --git a/Mi_Lua/luci/view/web/plugins/guest.htm b/Mi_Lua/luci/view/web/plugins/guest.htm new file mode 100644 index 0000000..74802d7 --- /dev/null +++ b/Mi_Lua/luci/view/web/plugins/guest.htm @@ -0,0 +1,178 @@ +<% +--[[ + Info 迅雷插件 +]]-- + +local ver = require("xiaoqiang.XQVersion").webVersion +%> +<%include('web/inc/head')%> +小米路由器 + + + + +
+ +
+
+

开启共享模式

+

新连接的设备允许访问小米路由盘

+
+
+ +
+
+
+
+

数据修改保护

+

开启保护后,小米路由盘上的数据不可修改

+
+
+ +
+
+ +
+<%include('web/inc/g.js.base')%> + + + \ No newline at end of file diff --git a/Mi_Lua/luci/view/web/plugins/kuaipan.htm b/Mi_Lua/luci/view/web/plugins/kuaipan.htm new file mode 100644 index 0000000..5bde2b6 --- /dev/null +++ b/Mi_Lua/luci/view/web/plugins/kuaipan.htm @@ -0,0 +1,521 @@ +<% +--[[ + Info 快盘插件 +]]-- + +local ver = require("xiaoqiang.XQVersion").webVersion +%> +<%include('web/inc/head')%> +路由器云备份 + + + + + +
+ + + +
+ + +
+ +
+ + + + +
+ +
+ + +
+ + + +
+ +<%include('web/inc/g.js.base')%> + + + diff --git a/Mi_Lua/luci/view/web/plugins/xunlei.htm b/Mi_Lua/luci/view/web/plugins/xunlei.htm new file mode 100644 index 0000000..b299f4f --- /dev/null +++ b/Mi_Lua/luci/view/web/plugins/xunlei.htm @@ -0,0 +1,22 @@ +<% +--[[ + Info 迅雷插件 +]]-- + +local ver = require("xiaoqiang.XQVersion").webVersion +%> +<%include('web/inc/head')%> +小米路由器 + + + + + + +<%include('web/inc/g.js.base')%> + + \ No newline at end of file diff --git a/Mi_Lua/luci/view/web/safeurl.htm b/Mi_Lua/luci/view/web/safeurl.htm new file mode 100644 index 0000000..0398cc5 --- /dev/null +++ b/Mi_Lua/luci/view/web/safeurl.htm @@ -0,0 +1,16 @@ + + + + + +

+ go on visite +

+ +
diff --git a/Mi_Lua/luci/view/web/setting.htm b/Mi_Lua/luci/view/web/setting.htm new file mode 100644 index 0000000..0476f36 --- /dev/null +++ b/Mi_Lua/luci/view/web/setting.htm @@ -0,0 +1,290 @@ +<% + local ver = require("xiaoqiang.XQVersion").webVersion +%> +<%include ("web/inc/head")%> +路由设置 - 小米路由器 + + + + +<%include ("web/inc/g.js.base")%> +<%include ("web/inc/g.js")%> +<%include ("web/inc/reboot.js")%> + + + diff --git a/Mi_Lua/luci/view/web/setting/ddns.htm b/Mi_Lua/luci/view/web/setting/ddns.htm new file mode 100644 index 0000000..d58e523 --- /dev/null +++ b/Mi_Lua/luci/view/web/setting/ddns.htm @@ -0,0 +1,409 @@ +<% +--[[ + Info DDNS +]]-- + +local ver = require("xiaoqiang.XQVersion").webVersion +local request_uri = luci.http.getenv("REQUEST_URI") +%> +<%include('web/inc/head')%> +小米路由器 + + + + +
+
+

DDNS

+ +
+
+
+ + +
+
+
+<%include('web/inc/g.js.base')%> + + diff --git a/Mi_Lua/luci/view/web/setting/developer.htm b/Mi_Lua/luci/view/web/setting/developer.htm new file mode 100644 index 0000000..e7df355 --- /dev/null +++ b/Mi_Lua/luci/view/web/setting/developer.htm @@ -0,0 +1,165 @@ +<% +--[[ + Info Developer +]]-- +local ver = require("xiaoqiang.XQVersion").webVersion +%> +<%include('web/inc/head')%> +小米路由器 + + + + +
+
+

开发者选项

+
+
+
+
+ + + +
+
" class="form form-horizontal" method="post" name="developer" id="developer" hidden='hidden'> +
+ + + +
+
+
+
+ +
+
+
+

通过SSH工具,输入ssh plugin@miwifi.com -p 2222 登录路由器,默认登录密码admin(登录后可通过passwd修改密码)

+
+
+
+
+ +<%include('web/inc/g.js.base')%> + diff --git a/Mi_Lua/luci/view/web/setting/diskformat.htm b/Mi_Lua/luci/view/web/setting/diskformat.htm new file mode 100644 index 0000000..eca9118 --- /dev/null +++ b/Mi_Lua/luci/view/web/setting/diskformat.htm @@ -0,0 +1,70 @@ +<% +--[[ + Info 格式化小强盘 +]]-- + +local ver = require("xiaoqiang.XQVersion").webVersion +local request_uri = luci.http.getenv("REQUEST_URI") +%> +<%include('web/inc/head')%> +小米路由器 + + + +
+
+

格式化硬盘

+
+
+
+
+

格式化硬盘

+

执行该任务硬盘里所有的数据将被清空,格式化前确保数据已备份。

+
+
+ +
+
+
+
+<%include('web/inc/g.js.base')%> + + + \ No newline at end of file diff --git a/Mi_Lua/luci/view/web/setting/dmz.htm b/Mi_Lua/luci/view/web/setting/dmz.htm new file mode 100644 index 0000000..359f8ae --- /dev/null +++ b/Mi_Lua/luci/view/web/setting/dmz.htm @@ -0,0 +1,236 @@ +<% +--[[ + Info DMZ +]]-- + +local ver = require("xiaoqiang.XQVersion").webVersion +local request_uri = luci.http.getenv("REQUEST_URI") + +%> +<%include('web/inc/head')%> +小米路由器 + + + + +
+
+

DMZ

+ +
+
+ +
+ 开启DMZ功能可以将内网某一个设备的IP映射到外网,方便从外网访问到该设备。 +
+ + +
+
+<%include('web/inc/g.js.base')%> + diff --git a/Mi_Lua/luci/view/web/setting/lamp.htm b/Mi_Lua/luci/view/web/setting/lamp.htm new file mode 100644 index 0000000..9f85987 --- /dev/null +++ b/Mi_Lua/luci/view/web/setting/lamp.htm @@ -0,0 +1,156 @@ +<% +--[[ + Info 路由器重启恢复 +]]-- + +local ver = require("xiaoqiang.XQVersion").webVersion +local request_uri = luci.http.getenv("REQUEST_URI") +%> +<%include('web/inc/head')%> +小米路由器 + + + + +
+
+

LAMP

+
+
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+<%include('web/inc/g.js.base')%> + diff --git a/Mi_Lua/luci/view/web/setting/log.htm b/Mi_Lua/luci/view/web/setting/log.htm new file mode 100644 index 0000000..a6bf85f --- /dev/null +++ b/Mi_Lua/luci/view/web/setting/log.htm @@ -0,0 +1,54 @@ +<% + local ver = require("xiaoqiang.XQVersion").webVersion +%> +<%include('web/inc/head')%> +小米路由器 + + + +
+
+

上传日志

+
+
+
+
+ +
+
+
+
+<%include('web/inc/g.js.base')%> + + + \ No newline at end of file diff --git a/Mi_Lua/luci/view/web/setting/nat.htm b/Mi_Lua/luci/view/web/setting/nat.htm new file mode 100644 index 0000000..d1c5612 --- /dev/null +++ b/Mi_Lua/luci/view/web/setting/nat.htm @@ -0,0 +1,592 @@ +<% +--[[ + Info nat +]]-- + +local ver = require("xiaoqiang.XQVersion").webVersion +local request_uri = luci.http.getenv("REQUEST_URI") +%> +<%include('web/inc/head')%> +小米路由器 + + + + +
+
+

端口转发

+ +
+
+ +
+

端口转发

+
+
+ + + +
+ +
+ + + + + +
+ +
+ + + +
+ +
+ + + +
+ +
+ + + +
+ +
+ +
+
+
+ +
+

规则列表

+
+ + + + + + + + + + + + + + + +
名称协议外部端口内部IP地址内部端口操作
+
+
+ +
+

范围转发

+
+ +
+ + + +
+ +
+ + + + + +
+ +
+ + + +
+ +
+ + + +
+ +
+ + + +
+ +
+ +
+
+
+ +
+

规则列表

+
+ + + + + + + + + + + + + +
名称协议起始端口结束端口目标IP操作
+
+
+
+
+ +
+
+
+
+ + + + + +<%include('web/inc/g.js.base')%> + diff --git a/Mi_Lua/luci/view/web/setting/net_ipmacband.htm b/Mi_Lua/luci/view/web/setting/net_ipmacband.htm new file mode 100644 index 0000000..80d5ddd --- /dev/null +++ b/Mi_Lua/luci/view/web/setting/net_ipmacband.htm @@ -0,0 +1,317 @@ +<% +--[[ + Info upnp +]]-- + +local ver = require("xiaoqiang.XQVersion").webVersion +local request_uri = luci.http.getenv("REQUEST_URI") +%> +<%include('web/inc/head')%> +小米路由器 + + + + +
+
+

DHCP静态IP分配

+
+
+
+
+

当前连接设备列表:

+ + + + + + + + + + + + +
设备名称IP地址MAC地址操作
+
+ +
+

绑定列表:可以从设备列表添加或者手工添加

+ + + + + + + + + + + +
IP地址MAC地址操作 全部解绑
+
+ +
+ +
+ +
+
+
+ + + +<%include('web/inc/g.js.base')%> + \ No newline at end of file diff --git a/Mi_Lua/luci/view/web/setting/net_lan.htm b/Mi_Lua/luci/view/web/setting/net_lan.htm new file mode 100644 index 0000000..22aa40c --- /dev/null +++ b/Mi_Lua/luci/view/web/setting/net_lan.htm @@ -0,0 +1,310 @@ +<% +--[[ + Info lan 口设置 +]]-- + +local ver = require("xiaoqiang.XQVersion").webVersion +local request_uri = luci.http.getenv("REQUEST_URI") +%> +<%include('web/inc/head')%> +小米路由器 + + + + +
+
+

内网设置

+
+
+
+
+ +
+ + + +
+
+ +
+
+
+
+
+
+
+ + + +
+
"> + + + +
+ + + 192.168.1. - + + +
+
+ + + + + + +
+
+ +
+
+
+
+
+
+
+<%include('web/inc/g.js.base')%> + + + diff --git a/Mi_Lua/luci/view/web/setting/net_setup_mac.htm b/Mi_Lua/luci/view/web/setting/net_setup_mac.htm new file mode 100644 index 0000000..8d0028c --- /dev/null +++ b/Mi_Lua/luci/view/web/setting/net_setup_mac.htm @@ -0,0 +1,134 @@ +<% +--[[ + Info MAC 地址绑定 +]]-- + +local remote_addr = luci.http.getenv("REMOTE_ADDR") +local mac = string.upper(luci.sys.net.ip4mac(remote_addr)) +local ver = require("xiaoqiang.XQVersion").webVersion +local request_uri = luci.http.getenv("REQUEST_URI") +%> +<%include('web/inc/head')%> +小米路由器 + + + +
+
+

MAC地址克隆

+
+
+
+

你路由器当前使用的MAC地址是:获取中...
如需克隆MAC地址,请做如下操作:

+
+
+ + + + + +
+ +
+ +
+
+
+
+
+<%include('web/inc/g.js.base')%> + + + diff --git a/Mi_Lua/luci/view/web/setting/net_wan.htm b/Mi_Lua/luci/view/web/setting/net_wan.htm new file mode 100644 index 0000000..c917794 --- /dev/null +++ b/Mi_Lua/luci/view/web/setting/net_wan.htm @@ -0,0 +1,576 @@ +<% +--[[ + Info 网络连接设置 +]]-- +local ver = require("xiaoqiang.XQVersion").webVersion +local XQLanWanUtil = require("xiaoqiang.util.XQLanWanUtil") +local is_conn = XQLanWanUtil.getWanMonitorStat().WANLINKSTAT == "UP" +local autotype = luci.http.formvalue("mini") +local wanType = 2 +local getWanType = XQLanWanUtil.getAutoWanType() +if getWanType then + wanType = getWanType +end +local wan = XQLanWanUtil.getLanWanInfo("wan") +%> +<%include('web/inc/head')%> +小米路由器 + +<%if autotype ~= nil then%> + +<%end%> + + +
+
+

外网设置

+
+
+
+ +
+
+
+

+
+
+ + + + +
+
+ +
+ + + + + +
+ +
+
+ +
+
+
+
+<%include('web/inc/g.js.base')%> + + + \ No newline at end of file diff --git a/Mi_Lua/luci/view/web/setting/nginx.htm b/Mi_Lua/luci/view/web/setting/nginx.htm new file mode 100644 index 0000000..09893ad --- /dev/null +++ b/Mi_Lua/luci/view/web/setting/nginx.htm @@ -0,0 +1,104 @@ +<% +--[[ + Info 路由器重启恢复 +]]-- + +local ver = require("xiaoqiang.XQVersion").webVersion +local request_uri = luci.http.getenv("REQUEST_URI") +%> +<%include('web/inc/head')%> +小米路由器 + + + + +
+
+

关闭NGINX

+
+
+ +
+
+

关闭NGINX

+

关闭NGINX,可以禁止路由器预加载功能,专供专业人士使用。

+
+
+
NGINX状态检测中...
+ +
+
+
+
+<%include('web/inc/g.js.base')%> + \ No newline at end of file diff --git a/Mi_Lua/luci/view/web/setting/noflushd.htm b/Mi_Lua/luci/view/web/setting/noflushd.htm new file mode 100644 index 0000000..15f2c64 --- /dev/null +++ b/Mi_Lua/luci/view/web/setting/noflushd.htm @@ -0,0 +1,75 @@ +<% +--[[ + Info Noflushd +]]-- +local ver = require("xiaoqiang.XQVersion").webVersion +%> + +<%include('web/inc/head')%> +小米路由器 + + + + +
+
+

硬盘自动休眠

+ +
+
+
+

开启此功能后小米路由器将在硬盘非工作状态下使其进入休眠状态,以延长硬盘使用寿命。

+

(建议正在使用SSH连接路由器的用户不要开启此功能)

+
+
+
+ +<%include('web/inc/g.js.base')%> + diff --git a/Mi_Lua/luci/view/web/setting/passport.htm b/Mi_Lua/luci/view/web/setting/passport.htm new file mode 100644 index 0000000..953639c --- /dev/null +++ b/Mi_Lua/luci/view/web/setting/passport.htm @@ -0,0 +1,143 @@ +<% +--[[ + Info 路由器权限 +]]-- + +local ver = require("xiaoqiang.XQVersion").webVersion +local request_uri = luci.http.getenv("REQUEST_URI") +%> +<%include('web/inc/head')%> +小米路由器 + + + + +
+
+

修改管理密码

+
+
+
+
" class="form form-horizontal" method="post" name="passport" id="passport"> + + +
+ + + +
+
+ + + +
+
+ + + +
+
+ +
+
+
+
+
+<%include('web/inc/g.js.base')%> + \ No newline at end of file diff --git a/Mi_Lua/luci/view/web/setting/predownload.htm b/Mi_Lua/luci/view/web/setting/predownload.htm new file mode 100644 index 0000000..6e25bf7 --- /dev/null +++ b/Mi_Lua/luci/view/web/setting/predownload.htm @@ -0,0 +1,74 @@ +<% +--[[ + Info Predownload +]]-- +local ver = require("xiaoqiang.XQVersion").webVersion +%> + +<%include('web/inc/head')%> +小米路由器 + + + + +
+
+

升级包预下载

+ +
+
+
+

开启此功能后后台会自动检测是否有可用升级包并自动下载。

+
+
+
+ +<%include('web/inc/g.js.base')%> + diff --git a/Mi_Lua/luci/view/web/setting/qos.htm b/Mi_Lua/luci/view/web/setting/qos.htm new file mode 100644 index 0000000..acc63d1 --- /dev/null +++ b/Mi_Lua/luci/view/web/setting/qos.htm @@ -0,0 +1,233 @@ +<% +--[[ + Info 应用限速 +]]-- + +local ver = require("xiaoqiang.XQVersion").webVersion +local request_uri = luci.http.getenv("REQUEST_URI") +%> +<%include('web/inc/head')%> +小米路由器 + + + + +
+
+

应用限速状态

+ +
+
+
+ +
+
+
+<%include('web/inc/g.js.base')%> + \ No newline at end of file diff --git a/Mi_Lua/luci/view/web/setting/qos_pro.htm b/Mi_Lua/luci/view/web/setting/qos_pro.htm new file mode 100644 index 0000000..9b89430 --- /dev/null +++ b/Mi_Lua/luci/view/web/setting/qos_pro.htm @@ -0,0 +1,606 @@ +<% +--[[ + Info Qos +]]-- + +local ver = require("xiaoqiang.XQVersion").webVersion +local request_uri = luci.http.getenv("REQUEST_URI") +%> +<%include('web/inc/head')%> +小米路由器 + + + + +
+
+

QoS状态

+ +
+
+ + +
+
+ + +<%include('web/inc/g.js.base')%> + \ No newline at end of file diff --git a/Mi_Lua/luci/view/web/setting/reboot.htm b/Mi_Lua/luci/view/web/setting/reboot.htm new file mode 100644 index 0000000..66b8c40 --- /dev/null +++ b/Mi_Lua/luci/view/web/setting/reboot.htm @@ -0,0 +1,50 @@ +<% +--[[ + Info 路由器重启恢复 +]]-- + +local ver = require("xiaoqiang.XQVersion").webVersion +local request_uri = luci.http.getenv("REQUEST_URI") +%> +<%include('web/inc/head')%> +小米路由器 + + + + +
+
+

路由器关机&重启

+
+
+
+
+

重启路由器

+

路由器重启需要等待几十秒或更多时间,重启过程中,将会断开网络连接,稍后将自动重新连接网络。

+
+
+ +
+
+
+
+

关闭路由器

+

关闭路由器将断开其他设备的数据访问和网络连接,之后便可以安全的断开电源。(再次启动需要手工连接电源)

+
+
+ +
+
+
+
+<%include('web/inc/g.js.base')%> + \ No newline at end of file diff --git a/Mi_Lua/luci/view/web/setting/reset.htm b/Mi_Lua/luci/view/web/setting/reset.htm new file mode 100644 index 0000000..13f540a --- /dev/null +++ b/Mi_Lua/luci/view/web/setting/reset.htm @@ -0,0 +1,40 @@ +<% +--[[ + Info 路由器恢复出厂设置 +]]-- + +local ver = require("xiaoqiang.XQVersion").webVersion +local request_uri = luci.http.getenv("REQUEST_URI") +%> +<%include('web/inc/head')%> +小米路由器 + + + + +
+
+

恢复出厂设置

+
+
+
+
+

恢复出厂设置

+

清除路由器上所有的设置项目,包括绑定的小米账号,恢复到出厂状态。

+

+
+
+ +
+
+
+
+<%include('web/inc/g.js.base')%> + \ No newline at end of file diff --git a/Mi_Lua/luci/view/web/setting/sys_psp.htm b/Mi_Lua/luci/view/web/setting/sys_psp.htm new file mode 100644 index 0000000..27aafd6 --- /dev/null +++ b/Mi_Lua/luci/view/web/setting/sys_psp.htm @@ -0,0 +1,196 @@ +<% +--[[ + Info 绑定小米账号 +]]-- + +local XQSysUtil = require "xiaoqiang.util.XQSysUtil" +local ver = require("xiaoqiang.XQVersion").webVersion +local isBinded = (XQSysUtil.getPassportBindInfo() and 'true') or 'false'; +%> +<%include('web/inc/head')%> +小米路由器 + + + +
+
+

小米账号管理

+
+
+
+ + +
+
+
+ + + +<%include('web/inc/g.js.base')%> + + + + diff --git a/Mi_Lua/luci/view/web/setting/sys_status.htm b/Mi_Lua/luci/view/web/setting/sys_status.htm new file mode 100644 index 0000000..f37a632 --- /dev/null +++ b/Mi_Lua/luci/view/web/setting/sys_status.htm @@ -0,0 +1,113 @@ +<% +--[[ + Info 系统状态 +]]-- + +local ver = require("xiaoqiang.XQVersion").webVersion +local XQSysUtil = require("xiaoqiang.util.XQSysUtil") +local romVersion = XQSysUtil.getRomVersion() +local hardwareVersion = XQSysUtil.getHardwareGPIO() +local request_uri = luci.http.getenv("REQUEST_URI") +%> +<%include('web/inc/head')%> +小米路由器 + + + +
+
+

系统状态

+
+
+
+
+

版本信息

+
+
    +
  • 当前软件版本:<%=romVersion%>
  • +
  • 当前硬件版本:<%=hardwareVersion%>
  • +
+
+
+
+

LAN口状态

+
+
    +
  • MAC地址:
  • +
  • IP地址:
  • +
  • 子网掩码:
  • +
+
+
+
+

无线状态

+
+
    +
  • WiFi 2.4G:
  • +
  • SSID:
  • +
  • 信道:
  • +
  • 模式:
  • + +
+
    +
  • WiFi 5G:
  • +
  • SSID:
  • +
  • 信道:
  • +
  • 模式:
  • + +
+
+
+
+

WAN口状态

+
+
    +
  • MAC地址:
  • +
  • IP地址:
  • +
  • 连接类型:
  • +
  • 子网掩码:
  • +
  • 网关:
  • +
  • DNS服务器:
  • +
  • 备选DNS:
  • +
+
+
+
+
+
+<%include('web/inc/g.js.base')%> + + + \ No newline at end of file diff --git a/Mi_Lua/luci/view/web/setting/upgrade.htm b/Mi_Lua/luci/view/web/setting/upgrade.htm new file mode 100644 index 0000000..b60e613 --- /dev/null +++ b/Mi_Lua/luci/view/web/setting/upgrade.htm @@ -0,0 +1,76 @@ + <% +--[[ + Info 路由器固件升级 +]]-- + +local XQSysUtil = require "xiaoqiang.util.XQSysUtil" +local ver = require("xiaoqiang.XQVersion").webVersion +local XQSysUtil = require("xiaoqiang.util.XQSysUtil") +local romVersion = XQSysUtil.getRomVersion() +local request_uri = luci.http.getenv("REQUEST_URI") +%> +<%include('web/inc/head')%> +小米路由器 + + + + +
+
+

升级检测

+
+
+
+

当前版本<%=romVersion%>正在检测更新,请稍候...

+ + + + +
+
+
+ +<%include('web/inc/g.js.base')%> + + + \ No newline at end of file diff --git a/Mi_Lua/luci/view/web/setting/upgrade_manual.htm b/Mi_Lua/luci/view/web/setting/upgrade_manual.htm new file mode 100644 index 0000000..33b7b03 --- /dev/null +++ b/Mi_Lua/luci/view/web/setting/upgrade_manual.htm @@ -0,0 +1,139 @@ +<% +local ver = require("xiaoqiang.XQVersion").webVersion +local request_uri = luci.http.getenv("REQUEST_URI") +%> +<%include('web/inc/head')%> +小米路由器 + + + +
+
+

路由器固件手动升级

+
+
+
+
+

如因故无法进行系统OTA升级,或者您需要将路由器软件降级到前一版本时,您可以使用手工方式上传并安装固件。

+

注意:升级过程中请勿断开路由器电源,否则将导致路由器损坏而无法使用;升级完成后,路由器将会自动重新启动。

+
+
+
+ + + + + +
+
+ +
+
+
+
+
+<%include('web/inc/g.js.base')%> + + + \ No newline at end of file diff --git a/Mi_Lua/luci/view/web/setting/upload_config.htm b/Mi_Lua/luci/view/web/setting/upload_config.htm new file mode 100644 index 0000000..3927482 --- /dev/null +++ b/Mi_Lua/luci/view/web/setting/upload_config.htm @@ -0,0 +1,42 @@ +<% + local ver = require("xiaoqiang.XQVersion").webVersion + local XQSysUtil = require "xiaoqiang.util.XQSysUtil" + local config = XQSysUtil.getConfigInfo() +%> +<%include('web/inc/head')%> +小米路由器 + + + +
+
+

上传配置信息

+
+
+
+
+
<%=config%>
+
+
+ +
+ +
+
+
+<%include('web/inc/g.js.base')%> + + + \ No newline at end of file diff --git a/Mi_Lua/luci/view/web/setting/upnp.htm b/Mi_Lua/luci/view/web/setting/upnp.htm new file mode 100644 index 0000000..a6ba31b --- /dev/null +++ b/Mi_Lua/luci/view/web/setting/upnp.htm @@ -0,0 +1,96 @@ +<% +--[[ + Info upnp +]]-- + +local ver = require("xiaoqiang.XQVersion").webVersion +local request_uri = luci.http.getenv("REQUEST_URI") +%> +<%include('web/inc/head')%> +小米路由器 + + + + +
+
+

UPnP状态

+ +
+
+ +
+

UPnP设备列表:

+ + + + + + + + + + + + + +
协议应用名称客户端IP内部端口外部端口
+
+
+
+<%include('web/inc/g.js.base')%> + \ No newline at end of file diff --git a/Mi_Lua/luci/view/web/setting/vpn.htm b/Mi_Lua/luci/view/web/setting/vpn.htm new file mode 100644 index 0000000..ce4d226 --- /dev/null +++ b/Mi_Lua/luci/view/web/setting/vpn.htm @@ -0,0 +1,228 @@ +<% +--[[ + Info PPTP/L2TP +]]-- + +local ver = require("xiaoqiang.XQVersion").webVersion +local request_uri = luci.http.getenv("REQUEST_URI") +%> +<%include('web/inc/head')%> +小米路由器 + + + + +
+
+

PPTP/L2TP

+
+
+
+
" class="form form-horizontal" method="post" name="vpn" id="vpn"> +
+ + + + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ +
+
+ +
+
+ +
+

当前状态:查询中...

+

+
+
+
+
+<%include('web/inc/g.js.base')%> + diff --git a/Mi_Lua/luci/view/web/setting/wifi_filter.htm b/Mi_Lua/luci/view/web/setting/wifi_filter.htm new file mode 100644 index 0000000..6d6d640 --- /dev/null +++ b/Mi_Lua/luci/view/web/setting/wifi_filter.htm @@ -0,0 +1,395 @@ +<% +--[[ + Info upnp +]]-- + +local ver = require("xiaoqiang.XQVersion").webVersion +local wifiUtil = require("xiaoqiang.util.XQWifiUtil") +local request_uri = luci.http.getenv("REQUEST_URI") +local remote_addr = luci.http.getenv("REMOTE_ADDR") +local mac = luci.sys.net.ip4mac(remote_addr) +local lanType = wifiUtil.getDeviceWifiIndex(mac) +%> +<%include('web/inc/head')%> +小米路由器 + + + + +
+
+

无线访问控制

+ +
+
+ +
+
+

控制模式:

+

+ + +

+
+
+

设备列表:

+ + + + + + + + + + + +
当前连接设备设备信息连接时长连接速度操作
+
+
+

无线访问控制名单:

+ + + + + + + + +
设备信息操作
+
+
+ +
+
+
+
+ + +<%include('web/inc/g.js.base')%> + \ No newline at end of file diff --git a/Mi_Lua/luci/view/web/setting/wifi_set.htm b/Mi_Lua/luci/view/web/setting/wifi_set.htm new file mode 100644 index 0000000..64d4d7e --- /dev/null +++ b/Mi_Lua/luci/view/web/setting/wifi_set.htm @@ -0,0 +1,544 @@ +<% +--[[ + Info wifi 基本设置 +]]-- +function escape(str) + str = string.gsub( str, '<', '<') + str = string.gsub( str, '>', '>') + str = string.gsub( str, '"', '"') + str = string.gsub( str, "'", ''') + str = string.gsub( str, '&', '&') + return str +end +local ver = require("xiaoqiang.XQVersion").webVersion +local wifiUtil = require("xiaoqiang.util.XQWifiUtil") +local wifi_status = wifiUtil.getWifiStatus(1).up +local wifi5_status = wifiUtil.getWifiStatus(2).up + +local remote_addr = luci.http.getenv("REMOTE_ADDR") +local mac = luci.sys.net.ip4mac(remote_addr) +local lanType = wifiUtil.getDeviceWifiIndex(mac) +%> +<%include('web/inc/head')%> +小米路由器 + + + +
+
+

WiFi设置

+
+
+
+
+
" autocomplete="off"> +
+
+
+
+ +
+
+
+
+
+
+<%include('web/inc/g.js.base')%> + + + + + diff --git a/Mi_Lua/luci/view/web/setting/wifi_set_mini.htm b/Mi_Lua/luci/view/web/setting/wifi_set_mini.htm new file mode 100644 index 0000000..bd347a0 --- /dev/null +++ b/Mi_Lua/luci/view/web/setting/wifi_set_mini.htm @@ -0,0 +1,324 @@ +<% +--[[ + Info wifi 基本设置 +]]-- +local ver = require("xiaoqiang.XQVersion").webVersion +local wifiUtil = require("xiaoqiang.util.XQWifiUtil") +local wifiIndex = tonumber(luci.http.formvalue("wifi_device")) +if not wifiIndex then + wifiIndex = 1 +end +local wifi_status = wifiUtil.getWifiStatus(wifiIndex).up +%> +<%include('web/inc/head')%> +小米路由器 + + + +
+
+

开启关闭WIFI,设置WIFI名称,密码等

+
+
+
+
+ + + +
+
+
+
+
+<%include('web/inc/g.js.base')%> + + + + diff --git a/Mi_Lua/luci/view/web/setting/wifi_set_pro.htm b/Mi_Lua/luci/view/web/setting/wifi_set_pro.htm new file mode 100644 index 0000000..efaf9aa --- /dev/null +++ b/Mi_Lua/luci/view/web/setting/wifi_set_pro.htm @@ -0,0 +1,441 @@ +<% +--[[ + Info wifi 高级设置 +]]-- +local LuciJson = require("luci.json") +local ver = require("xiaoqiang.XQVersion").webVersion +local wifiUtil = require("xiaoqiang.util.XQWifiUtil") +local pddk24 = LuciJson.encode(wifiUtil.getDefaultWifiChannels(1)); +local pddk50 = LuciJson.encode(wifiUtil.getDefaultWifiChannels(2)); +local guestwifi1 = LuciJson.encode(wifiUtil.getGuestWifi(1)) +%> +<%include('web/inc/head')%> +小米路由器 + + + +
+
+

无线信道与强度

+
+
+
+
" method="post"> + +
+ + + + + +
+
+ + + + +
+ +
+ + + + + +
+
+ +
+
+
+ +
+ +
+
" method="post"> + +
+ + + + + +
+
+ + + + + +
+ +
+ + + + + +
+ +
+ +
+
+
+
+
+<%include('web/inc/g.js.base')%> + + + diff --git a/Mi_Lua/luci/view/web/setting/wifi_setup_channel.htm b/Mi_Lua/luci/view/web/setting/wifi_setup_channel.htm new file mode 100644 index 0000000..e061580 --- /dev/null +++ b/Mi_Lua/luci/view/web/setting/wifi_setup_channel.htm @@ -0,0 +1,187 @@ +<% +--[[ + Info wifi 频道强度设置 +]]-- + +local ver = require("xiaoqiang.XQVersion").webVersion +local request_uri = luci.http.getenv("REQUEST_URI") +%> +<%include('web/inc/head')%> +小米路由器 + + +
+
+

WiFi信道强度设置

+
+
+
+
+
+ + + + +
+ + + + + +
+
+ + + + + +
+
+ + + + + +
+
+ + + + + +
+
+ +
+
+
+
+
+<%include('web/inc/g.js.base')%> + + + diff --git a/Mi_Lua/luci/view/web/setting/wifi_txpwr.htm b/Mi_Lua/luci/view/web/setting/wifi_txpwr.htm new file mode 100644 index 0000000..82734c7 --- /dev/null +++ b/Mi_Lua/luci/view/web/setting/wifi_txpwr.htm @@ -0,0 +1,102 @@ +<% +--[[ + Info wifi 高级设置 +]]-- +local LuciJson = require("luci.json") +local ver = require("xiaoqiang.XQVersion").webVersion +local wifiUtil = require("xiaoqiang.util.XQWifiUtil") +local wifiTxpwr = wifiUtil.getWifiTxpwr(1) +%> +<%include('web/inc/head')%> +小米路由器 + + + +
+
+

WiFi信号强度

+
+
+
+
+
+ + + + + +
+ +
+ +
+
+
+
+ +<%include('web/inc/g.js.base')%> + + + \ No newline at end of file diff --git a/Mi_Lua/luci/view/web/sysauth.htm b/Mi_Lua/luci/view/web/sysauth.htm new file mode 100644 index 0000000..c99e344 --- /dev/null +++ b/Mi_Lua/luci/view/web/sysauth.htm @@ -0,0 +1,309 @@ +<% + local ver = require("xiaoqiang.XQVersion").webVersion + local XQSysUtil = require "xiaoqiang.util.XQSysUtil" + -- local XQSecureUtil = require "xiaoqiang.util.XQSecureUtil" + if not XQSysUtil.getInitInfo() then + luci.http.redirect(luci.dispatcher.build_url("web", "init","hello")) + end + + local router_name = XQSysUtil.getRouterName() or "小米路由器" + local isBinded = (XQSysUtil.getPassportBindInfo() and 'true') or 'false' + local logType = 1 + if logtype then + logType = tonumber(logtype) or 1 + end + + local remote_addr = luci.http.getenv("REMOTE_ADDR") + local mac = luci.sys.net.ip4mac(remote_addr) + -- local xmLoginUrl = XQSecureUtil.passportLoginUrl() +%> +<%include ("web/inc/head")%> +登录 - 小米路由器 + + + + +
+
+ +
+
+ +
+ <%include ("web/inc/footermini")%> +
+<%include ("web/inc/g.js.base")%> + + + + \ No newline at end of file diff --git a/Mi_Lua/luci/view/web/syslock.htm b/Mi_Lua/luci/view/web/syslock.htm new file mode 100644 index 0000000..17586d9 --- /dev/null +++ b/Mi_Lua/luci/view/web/syslock.htm @@ -0,0 +1,78 @@ +<% + local ver = require("xiaoqiang.XQVersion").webVersion +%> +<%include ("web/inc/head")%> +小米路由器 + + + + + +
+
+ +
+
+
+

路由器正在升级

+
+
+
+
+ 已更新0% +
+
+ +
+
+
+ <%include ("web/inc/footer")%> +
+<%include ("web/inc/g.js.base")%> + + + \ No newline at end of file diff --git a/Mi_Lua/luci/view/web/sysset.htm b/Mi_Lua/luci/view/web/sysset.htm new file mode 100644 index 0000000..03ba797 --- /dev/null +++ b/Mi_Lua/luci/view/web/sysset.htm @@ -0,0 +1,287 @@ +<% + local ver = require("xiaoqiang.XQVersion").webVersion +%> +<%include ("web/inc/head")%> +路由设置 - 小米路由器 + + + + +<%include ("web/inc/g.js.base")%> +<%include ("web/inc/g.js")%> +<%include ("web/inc/reboot.js")%> + + + diff --git a/Mi_Lua/luci/view/web/urldetection.htm b/Mi_Lua/luci/view/web/urldetection.htm new file mode 100644 index 0000000..883bf93 --- /dev/null +++ b/Mi_Lua/luci/view/web/urldetection.htm @@ -0,0 +1,88 @@ +<% + local ver = require("xiaoqiang.XQVersion").webVersion +%> +<%include ("web/inc/head")%> +小米路由器 + + + +
+
+
+

高级诊断

+
+
+
+ + +
+ +
+ +<%include ("web/inc/g.js.base")%> + \ No newline at end of file diff --git a/Mi_Lua/luci/view/web/xmaccount.htm b/Mi_Lua/luci/view/web/xmaccount.htm new file mode 100644 index 0000000..79d12c2 --- /dev/null +++ b/Mi_Lua/luci/view/web/xmaccount.htm @@ -0,0 +1,45 @@ +<% + local ver = require("xiaoqiang.XQVersion").webVersion + local XQConfigs = require "xiaoqiang.common.XQConfigs" + local LuciUtil = require "luci.util" + + local getDvidCmd = XQConfigs.THRIFT_TO_MQTT_GET_DEVICEID + local deviceId = LuciUtil.exec(getDvidCmd) +%> +<%include ("web/inc/head")%> +登录 - 小米路由器 + + +<%include ("web/inc/g.js.base")%> + + + + \ No newline at end of file diff --git a/Mi_Lua/mime.lua b/Mi_Lua/mime.lua new file mode 100644 index 0000000..169eda2 --- /dev/null +++ b/Mi_Lua/mime.lua @@ -0,0 +1,87 @@ +----------------------------------------------------------------------------- +-- MIME support for the Lua language. +-- Author: Diego Nehab +-- Conforming to RFCs 2045-2049 +-- RCS ID: $Id: mime.lua,v 1.29 2007/06/11 23:44:54 diego Exp $ +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Declare module and import dependencies +----------------------------------------------------------------------------- +local base = _G +local ltn12 = require("ltn12") +local mime = require("mime.core") +local io = require("io") +local string = require("string") +module("mime") + +-- encode, decode and wrap algorithm tables +encodet = {} +decodet = {} +wrapt = {} + +-- creates a function that chooses a filter by name from a given table +local function choose(table) + return function(name, opt1, opt2) + if base.type(name) ~= "string" then + name, opt1, opt2 = "default", name, opt1 + end + local f = table[name or "nil"] + if not f then + base.error("unknown key (" .. base.tostring(name) .. ")", 3) + else return f(opt1, opt2) end + end +end + +-- define the encoding filters +encodet['base64'] = function() + return ltn12.filter.cycle(b64, "") +end + +encodet['quoted-printable'] = function(mode) + return ltn12.filter.cycle(qp, "", + (mode == "binary") and "=0D=0A" or "\r\n") +end + +-- define the decoding filters +decodet['base64'] = function() + return ltn12.filter.cycle(unb64, "") +end + +decodet['quoted-printable'] = function() + return ltn12.filter.cycle(unqp, "") +end + +local function format(chunk) + if chunk then + if chunk == "" then return "''" + else return string.len(chunk) end + else return "nil" end +end + +-- define the line-wrap filters +wrapt['text'] = function(length) + length = length or 76 + return ltn12.filter.cycle(wrp, length, length) +end +wrapt['base64'] = wrapt['text'] +wrapt['default'] = wrapt['text'] + +wrapt['quoted-printable'] = function() + return ltn12.filter.cycle(qpwrp, 76, 76) +end + +-- function that choose the encoding, decoding or wrap algorithm +encode = choose(encodet) +decode = choose(decodet) +wrap = choose(wrapt) + +-- define the end-of-line normalization filter +function normalize(marker) + return ltn12.filter.cycle(eol, 0, marker) +end + +-- high level stuffing filter +function stuff() + return ltn12.filter.cycle(dot, 2) +end diff --git a/Mi_Lua/mime.so.1.0.2 b/Mi_Lua/mime.so.1.0.2 new file mode 100644 index 0000000000000000000000000000000000000000..61835c1455753773c5cb62c0c3f3dcc8cdd612ca GIT binary patch literal 11068 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 literal 0 HcmV?d00001 diff --git a/Mi_Lua/net_tools.so b/Mi_Lua/net_tools.so new file mode 100644 index 0000000000000000000000000000000000000000..4750981d84d06db8d0166df8f36df7fe41099bd7 GIT binary patch literal 18800 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 K(o29nzW)Q$qz{<@ literal 0 HcmV?d00001 diff --git a/Mi_Lua/nixio.so b/Mi_Lua/nixio.so new file mode 100644 index 0000000000000000000000000000000000000000..75416c803a17e8ceaf9cec161e1185ec96254514 GIT binary patch literal 69136 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 E7uWb7m;e9( literal 0 HcmV?d00001 diff --git a/Mi_Lua/nixio/fs.lua b/Mi_Lua/nixio/fs.lua new file mode 100644 index 0000000..8883835 --- /dev/null +++ b/Mi_Lua/nixio/fs.lua @@ -0,0 +1,175 @@ +--[[ +nixio - Linux I/O library for lua + +Copyright 2009 Steven Barth + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +$Id$ +]]-- + +local table = require "table" +local nixio = require "nixio" +local type, ipairs, setmetatable = type, ipairs, setmetatable +require "nixio.util" + + +module ("nixio.fs", function(m) setmetatable(m, {__index = nixio.fs}) end) + + +function readfile(path, limit) + local fd, code, msg = nixio.open(path, "r") + local data + if not fd then + return nil, code, msg + end + + data, code, msg = fd:readall(limit) + + fd:close() + return data, code, msg +end + + +function writefile(path, data) + local fd, code, msg, stat = nixio.open(path, "w") + if not fd then + return nil, code, msg + end + + stat, code, msg = fd:writeall(data) + + fd:close() + return stat, code, msg +end + +function datacopy(src, dest, size) + local fdin, code, msg = nixio.open(src, "r") + if not fdin then + return nil, code, msg + end + + local fdout, code, msg = nixio.open(dest, "w") + if not fdout then + return nil, code, msg + end + + local stat, code, msg, sent = fdin:copy(fdout, size) + fdin:close() + fdout:close() + + return stat, code, msg, sent +end + +function copy(src, dest) + local stat, code, msg, res = nixio.fs.lstat(src) + if not stat then + return nil, code, msg + end + + if stat.type == "dir" then + if nixio.fs.stat(dest, type) ~= "dir" then + res, code, msg = nixio.fs.mkdir(dest) + else + stat = true + end + elseif stat.type == "lnk" then + res, code, msg = nixio.fs.symlink(nixio.fs.readlink(src), dest) + elseif stat.type == "reg" then + res, code, msg = datacopy(src, dest) + end + + if not res then + return nil, code, msg + end + + nixio.fs.utimes(dest, stat.atime, stat.mtime) + + if nixio.fs.lchown then + nixio.fs.lchown(dest, stat.uid, stat.gid) + end + + if stat.type ~= "lnk" then + nixio.fs.chmod(dest, stat.modedec) + end + + return true +end + +function move(src, dest) + local stat, code, msg = nixio.fs.rename(src, dest) + if not stat and code == nixio.const.EXDEV then + stat, code, msg = copy(src, dest) + if stat then + stat, code, msg = nixio.fs.unlink(src) + end + end + return stat, code, msg +end + +function mkdirr(dest, mode) + if nixio.fs.stat(dest, "type") == "dir" then + return true + else + local stat, code, msg = nixio.fs.mkdir(dest, mode) + if not stat and code == nixio.const.ENOENT then + stat, code, msg = mkdirr(nixio.fs.dirname(dest), mode) + if stat then + stat, code, msg = nixio.fs.mkdir(dest, mode) + end + end + return stat, code, msg + end +end + +local function _recurse(cb, src, dest) + local type = nixio.fs.lstat(src, "type") + if type ~= "dir" then + return cb(src, dest) + else + local stat, se, code, msg, s, c, m = true, nixio.const.sep + if dest then + s, c, m = cb(src, dest) + stat, code, msg = stat and s, c or code, m or msg + end + + for e in nixio.fs.dir(src) do + if dest then + s, c, m = _recurse(cb, src .. se .. e, dest .. se .. e) + else + s, c, m = _recurse(cb, src .. se .. e) + end + stat, code, msg = stat and s, c or code, m or msg + end + + if not dest then -- Postfix + s, c, m = cb(src) + stat, code, msg = stat and s, c or code, m or msg + end + + return stat, code, msg + end +end + +function copyr(src, dest) + return _recurse(copy, src, dest) +end + +function mover(src, dest) + local stat, code, msg = nixio.fs.rename(src, dest) + if not stat and code == nixio.const.EXDEV then + stat, code, msg = _recurse(copy, src, dest) + if stat then + stat, code, msg = _recurse(nixio.fs.remove, src) + end + end + return stat, code, msg +end + +function remover(src) + return _recurse(nixio.fs.remove, src) +end \ No newline at end of file diff --git a/Mi_Lua/nixio/util.lua b/Mi_Lua/nixio/util.lua new file mode 100644 index 0000000..63d2f62 --- /dev/null +++ b/Mi_Lua/nixio/util.lua @@ -0,0 +1,270 @@ +--[[ +nixio - Linux I/O library for lua + +Copyright 2009 Steven Barth + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +$Id$ +]]-- + +local table = require "table" +local nixio = require "nixio" +local getmetatable, assert, pairs, type = getmetatable, assert, pairs, type +local tostring = tostring + +module "nixio.util" + +local BUFFERSIZE = nixio.const.buffersize +local ZIOBLKSIZE = 65536 +local socket = nixio.meta_socket +local tls_socket = nixio.meta_tls_socket +local file = nixio.meta_file +local uname = nixio.uname() +local ZBUG = uname.sysname == "Linux" and uname.release:sub(1, 3) == "2.4" + +function consume(iter, append) + local tbl = append or {} + if iter then + for obj in iter do + tbl[#tbl+1] = obj + end + end + return tbl +end + +local meta = {} + +function meta.is_socket(self) + return (getmetatable(self) == socket) +end + +function meta.is_tls_socket(self) + return (getmetatable(self) == tls_socket) +end + +function meta.is_file(self) + return (getmetatable(self) == file) +end + +function meta.readall(self, len) + local block, code, msg = self:read(len or BUFFERSIZE) + + if not block then + return nil, code, msg, "" + elseif #block == 0 then + return "", nil, nil, "" + end + + local data, total = {block}, #block + + while not len or len > total do + block, code, msg = self:read(len and (len - total) or BUFFERSIZE) + + if not block then + return nil, code, msg, table.concat(data) + elseif #block == 0 then + break + end + + data[#data+1], total = block, total + #block + end + + local data = #data > 1 and table.concat(data) or data[1] + return data, nil, nil, data +end +meta.recvall = meta.readall + +function meta.writeall(self, data) + data = tostring(data) + local sent, code, msg = self:write(data) + + if not sent then + return nil, code, msg, 0 + end + + local total = sent + + while total < #data do + sent, code, msg = self:write(data, total) + + if not sent then + return nil, code, msg, total + end + + total = total + sent + end + + return total, nil, nil, total +end +meta.sendall = meta.writeall + +function meta.linesource(self, limit) + limit = limit or BUFFERSIZE + local buffer = "" + local bpos = 0 + return function(flush) + local line, endp, _ + + if flush then + line = buffer:sub(bpos + 1) + buffer = type(flush) == "string" and flush or "" + bpos = 0 + return line + end + + while not line do + _, endp, line = buffer:find("(.-)\r?\n", bpos + 1) + if line then + bpos = endp + return line + elseif #buffer < limit + bpos then + local newblock, code, msg = self:read(limit + bpos - #buffer) + if not newblock then + return nil, code, msg + elseif #newblock == 0 then + return nil + end + buffer = buffer:sub(bpos + 1) .. newblock + bpos = 0 + else + return nil, 0 + end + end + end +end + +function meta.blocksource(self, bs, limit) + bs = bs or BUFFERSIZE + return function() + local toread = bs + if limit then + if limit < 1 then + return nil + elseif limit < toread then + toread = limit + end + end + + local block, code, msg = self:read(toread) + + if not block then + return nil, code, msg + elseif #block == 0 then + return nil + else + if limit then + limit = limit - #block + end + + return block + end + end +end + +function meta.sink(self, close) + return function(chunk, src_err) + if not chunk and not src_err and close then + if self.shutdown then + self:shutdown() + end + self:close() + elseif chunk and #chunk > 0 then + return self:writeall(chunk) + end + return true + end +end + +function meta.copy(self, fdout, size) + local source = self:blocksource(nil, size) + local sink = fdout:sink() + local sent, chunk, code, msg = 0 + + repeat + chunk, code, msg = source() + sink(chunk, code, msg) + sent = chunk and (sent + #chunk) or sent + until not chunk + return not code and sent or nil, code, msg, sent +end + +function meta.copyz(self, fd, size) + local sent, lsent, code, msg = 0 + local splicable + + if not ZBUG and self:is_file() then + local ftype = self:stat("type") + if nixio.sendfile and fd:is_socket() and ftype == "reg" then + repeat + lsent, code, msg = nixio.sendfile(fd, self, size or ZIOBLKSIZE) + if lsent then + sent = sent + lsent + size = size and (size - lsent) + end + until (not lsent or lsent == 0 or (size and size == 0)) + if lsent or (not lsent and sent == 0 and + code ~= nixio.const.ENOSYS and code ~= nixio.const.EINVAL) then + return lsent and sent, code, msg, sent + end + elseif nixio.splice and not fd:is_tls_socket() and ftype == "fifo" then + splicable = true + end + end + + if nixio.splice and fd:is_file() and not splicable then + splicable = not self:is_tls_socket() and fd:stat("type") == "fifo" + end + + if splicable then + repeat + lsent, code, msg = nixio.splice(self, fd, size or ZIOBLKSIZE) + if lsent then + sent = sent + lsent + size = size and (size - lsent) + end + until (not lsent or lsent == 0 or (size and size == 0)) + if lsent or (not lsent and sent == 0 and + code ~= nixio.const.ENOSYS and code ~= nixio.const.EINVAL) then + return lsent and sent, code, msg, sent + end + end + + return self:copy(fd, size) +end + +if tls_socket then + function tls_socket.close(self) + return self.socket:close() + end + + function tls_socket.getsockname(self) + return self.socket:getsockname() + end + + function tls_socket.getpeername(self) + return self.socket:getpeername() + end + + function tls_socket.getsockopt(self, ...) + return self.socket:getsockopt(...) + end + tls_socket.getopt = tls_socket.getsockopt + + function tls_socket.setsockopt(self, ...) + return self.socket:setsockopt(...) + end + tls_socket.setopt = tls_socket.setsockopt +end + +for k, v in pairs(meta) do + file[k] = v + socket[k] = v + if tls_socket then + tls_socket[k] = v + end +end diff --git a/Mi_Lua/posix.so b/Mi_Lua/posix.so new file mode 100644 index 0000000000000000000000000000000000000000..f014dc0fff1ad9ffc9ba82fa84f8f9e6011e2fe0 GIT binary patch literal 41756 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 P$KOpy?t2-=mW%yATbp|K literal 0 HcmV?d00001 diff --git a/Mi_Lua/rc4.lua b/Mi_Lua/rc4.lua new file mode 100644 index 0000000..52d029e --- /dev/null +++ b/Mi_Lua/rc4.lua @@ -0,0 +1,77 @@ + +local string = require("string") +local bit = require("bit") + +module("rc4") + +local function swapStateIndicies(state) + local t = state.schedule[state.i] + state.schedule[state.i] = state.schedule[state.j] + state.schedule[state.j] = t +end + +local function swapIndicies(sch, i, j) + local t = sch[i] + sch[i] = sch[j] + sch[j] = t +end + +local function createKeySchedule(sKey) + local nKeyLength = string.len(sKey) + local tKey = { string.byte(sKey, 1, nKeyLength) } + if nKeyLength < 1 or nKeyLength > 256 then + error("Key length out of bounds. 1 <= length <= 256") + end + local tSch = {} + for i = 0, 255 do + tSch[i] = i + end + local j = 0 + for i = 0, 255 do + j = (j + tSch[i] + tKey[(i % nKeyLength) + 1]) % 256 + swapIndicies(tSch, i, j) + end + return tSch +end + +local function keyGeneration(state, nCount) + local K = {} + for i = 1, nCount do + state.i = (state.i + 1) % 256 + state.j = (state.j + state.schedule[state.i - 1]) % 256 + swapStateIndicies(state) + K[#K+1] = state.schedule[(state.schedule[ state.i - 1] + state.schedule[ state.j - 1] - 1) % 256] + end + return K +end + +local function cipher(sMessage, state) + local nCount = string.len(sMessage) + local K = keyGeneration(state, nCount) + local sOutput = "" + for i = 1, nCount do + sOutput = sOutput .. string.char(bit.bxor( K[i], string.byte(sMessage, i))) + end + return sOutput +end + +function new(sKey, bEncryption) + local tSch = createKeySchedule(sKey) + local nS1 = 0 + local nS2 = 0 + local state = { + i = nS1, + j = nS2, + schedule = tSch + } + local tSession = {} + local sFuncName = "decrypt" + if bEncryption then + sFuncName = "encrypt" + end + tSession[sFuncName] = function(sMessage) + local sOutput = cipher(sMessage, state) + return sOutput + end + return tSession +end \ No newline at end of file diff --git a/Mi_Lua/service/util/ServiceErrorUtil.lua b/Mi_Lua/service/util/ServiceErrorUtil.lua new file mode 100644 index 0000000..b83bf39 --- /dev/null +++ b/Mi_Lua/service/util/ServiceErrorUtil.lua @@ -0,0 +1,24 @@ +module ("service.util.ServiceErrorUtil", package.seeall) + +function getErrorMessage(errorCode) + local errorList = {} + + errorList[0] = _("") + errorList[1] = _("parameter missing") + errorList[2] = _("Parameter empty") + errorList[3] = _("Parameter format error") + errorList[5] = _("invalid app id") + + errorList[1056] = _("invalid device id") + errorList[1057] = _("resource is not ready") + + errorList[1559] = _("datacenter error") + + errorList[2010] = _("datacenter error") + + if (errorList[errorCode] == nil) then + return translate(_("未知错误")) + else + return translate(errorList[errorCode]) + end +end diff --git a/Mi_Lua/sha1.lua b/Mi_Lua/sha1.lua new file mode 100644 index 0000000..12971b7 --- /dev/null +++ b/Mi_Lua/sha1.lua @@ -0,0 +1,718 @@ +-- +-- SHA-1 secure hash computation, and HMAC-SHA1 signature computation, +-- in pure Lua (tested on Lua 5.1) +-- +-- Latest version always at: http://regex.info/blog/lua/sha1 +-- +-- Copyright 2009 Jeffrey Friedl +-- jfriedl@yahoo.com +-- http://regex.info/blog/ +-- +-- +-- Version 1 [May 28, 2009] +-- +-- +-- Lua is a pathetic, horrid, turd of a language. Not only doesn't it have +-- bitwise integer operators like OR and AND, it doesn't even have integers +-- (and those, relatively speaking, are its good points). Yet, this +-- implements the SHA-1 digest hash in pure Lua. While coding it, I felt as +-- if I were chiseling NAND gates out of rough blocks of silicon. Those not +-- already familiar with this woeful language may, upon seeing this code, +-- throw up in their own mouth. +-- +-- It's not super fast.... a 10k-byte message takes about 2 seconds on a +-- circa-2008 mid-level server, but it should be plenty adequate for short +-- messages, such as is often needed during authentication handshaking. +-- +-- Algorithm: http://www.itl.nist.gov/fipspubs/fip180-1.htm +-- +-- This file creates four entries in the global namespace: +-- +-- local hash_as_hex = sha1(message) -- returns a hex string +-- local hash_as_data = sha1_binary(message) -- returns raw bytes +-- +-- local hmac_as_hex = hmac_sha1(key, message) -- hex string +-- local hmac_as_data = hmac_sha1_binary(key, message) -- raw bytes +-- +-- Pass sha1() a string, and it returns a hash as a 40-character hex string. +-- For example, the call +-- +-- local hash = sha1 "http://regex.info/blog/" +-- +-- puts the 40-character string +-- +-- "7f103bf600de51dfe91062300c14738b32725db5" +-- +-- into the variable 'hash' +-- +-- Pass sha1_hmac() a key and a message, and it returns the signature as a +-- 40-byte hex string. +-- +-- +-- The two "_binary" versions do the same, but return the 20-byte string of raw data +-- that the 40-byte hex strings represent. +-- + +------------------------------------------------------------------------------------------ +------------------------------------------------------------------------------------------ +local string = require("string") +local table = require("table") +local math = require("math") +local base = _G + +module("sha1") + +-- +-- Return a W32 object for the number zero +-- +local function ZERO() + return { + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + false, false, false, false, false, false, false, false, + } +end + +local hex_to_bits = { + ["0"] = { false, false, false, false }, + ["1"] = { false, false, false, true }, + ["2"] = { false, false, true, false }, + ["3"] = { false, false, true, true }, + + ["4"] = { false, true, false, false }, + ["5"] = { false, true, false, true }, + ["6"] = { false, true, true, false }, + ["7"] = { false, true, true, true }, + + ["8"] = { true, false, false, false }, + ["9"] = { true, false, false, true }, + ["A"] = { true, false, true, false }, + ["B"] = { true, false, true, true }, + + ["C"] = { true, true, false, false }, + ["D"] = { true, true, false, true }, + ["E"] = { true, true, true, false }, + ["F"] = { true, true, true, true }, + + ["a"] = { true, false, true, false }, + ["b"] = { true, false, true, true }, + ["c"] = { true, true, false, false }, + ["d"] = { true, true, false, true }, + ["e"] = { true, true, true, false }, + ["f"] = { true, true, true, true }, +} + +-- +-- Given a string of 8 hex digits, return a W32 object representing that number +-- +local function from_hex(hex) + + base.assert(base.type(hex) == 'string') + base.assert(hex:match('^[0123456789abcdefABCDEF]+$')) + base.assert(#hex == 8) + + local W32 = { } + + for letter in hex:gmatch('.') do + local b = hex_to_bits[letter] + base.assert(b) + table.insert(W32, 1, b[1]) + table.insert(W32, 1, b[2]) + table.insert(W32, 1, b[3]) + table.insert(W32, 1, b[4]) + end + + return W32 +end + +local function COPY(old) + local W32 = { } + for k,v in base.pairs(old) do + W32[k] = v + end + + return W32 +end + +local function ADD(first, ...) + + local a = COPY(first) + + local C, b, sum + + for v = 1, base.select('#', ...) do + b = base.select(v, ...) + C = 0 + + for i = 1, #a do + sum = (a[i] and 1 or 0) + + (b[i] and 1 or 0) + + C + + if sum == 0 then + a[i] = false + C = 0 + elseif sum == 1 then + a[i] = true + C = 0 + elseif sum == 2 then + a[i] = false + C = 1 + else + a[i] = true + C = 1 + end + end + -- we drop any ending carry + + end + + return a +end + +local function XOR(first, ...) + + local a = COPY(first) + local b + for v = 1, base.select('#', ...) do + b = base.select(v, ...) + for i = 1, #a do + a[i] = a[i] ~= b[i] + end + end + + return a + +end + +local function AND(a, b) + + local c = ZERO() + + for i = 1, #a do + -- only need to set true bits; other bits remain false + if a[i] and b[i] then + c[i] = true + end + end + + return c +end + +local function OR(a, b) + + local c = ZERO() + + for i = 1, #a do + -- only need to set true bits; other bits remain false + if a[i] or b[i] then + c[i] = true + end + end + + return c +end + +local function OR3(a, b, c) + + local d = ZERO() + + for i = 1, #a do + -- only need to set true bits; other bits remain false + if a[i] or b[i] or c[i] then + d[i] = true + end + end + + return d +end + +local function NOT(a) + + local b = ZERO() + + for i = 1, #a do + -- only need to set true bits; other bits remain false + if not a[i] then + b[i] = true + end + end + + return b +end + +local function ROTATE(bits, a) + + local b = COPY(a) + + while bits > 0 do + bits = bits - 1 + table.insert(b, 1, table.remove(b)) + end + + return b + +end + + +local binary_to_hex = { + ["0000"] = "0", + ["0001"] = "1", + ["0010"] = "2", + ["0011"] = "3", + ["0100"] = "4", + ["0101"] = "5", + ["0110"] = "6", + ["0111"] = "7", + ["1000"] = "8", + ["1001"] = "9", + ["1010"] = "a", + ["1011"] = "b", + ["1100"] = "c", + ["1101"] = "d", + ["1110"] = "e", + ["1111"] = "f", +} + +function asHEX(a) + + local hex = "" + local i = 1 + while i < #a do + local binary = (a[i + 3] and '1' or '0') + .. + (a[i + 2] and '1' or '0') + .. + (a[i + 1] and '1' or '0') + .. + (a[i + 0] and '1' or '0') + + hex = binary_to_hex[binary] .. hex + + i = i + 4 + end + + return hex + +end + +local x67452301 = from_hex("67452301") +local xEFCDAB89 = from_hex("EFCDAB89") +local x98BADCFE = from_hex("98BADCFE") +local x10325476 = from_hex("10325476") +local xC3D2E1F0 = from_hex("C3D2E1F0") + +local x5A827999 = from_hex("5A827999") +local x6ED9EBA1 = from_hex("6ED9EBA1") +local x8F1BBCDC = from_hex("8F1BBCDC") +local xCA62C1D6 = from_hex("CA62C1D6") + + +function sha1(msg) + + base.assert(base.type(msg) == 'string') + base.assert(#msg < 0x7FFFFFFF) -- have no idea what would happen if it were large + + local H0 = x67452301 + local H1 = xEFCDAB89 + local H2 = x98BADCFE + local H3 = x10325476 + local H4 = xC3D2E1F0 + + local msg_len_in_bits = #msg * 8 + + local first_append = string.char(0x80) -- append a '1' bit plus seven '0' bits + + local non_zero_message_bytes = #msg +1 +8 -- the +1 is the appended bit 1, the +8 are for the final appended length + local current_mod = non_zero_message_bytes % 64 + local second_append = "" + if current_mod ~= 0 then + second_append = string.rep(string.char(0), 64 - current_mod) + end + + -- now to append the length as a 64-bit number. + local B1, R1 = math.modf(msg_len_in_bits / 0x01000000) + local B2, R2 = math.modf( 0x01000000 * R1 / 0x00010000) + local B3, R3 = math.modf( 0x00010000 * R2 / 0x00000100) + local B4 = 0x00000100 * R3 + + local L64 = string.char( 0) .. string.char( 0) .. string.char( 0) .. string.char( 0) -- high 32 bits + .. string.char(B1) .. string.char(B2) .. string.char(B3) .. string.char(B4) -- low 32 bits + + + + msg = msg .. first_append .. second_append .. L64 + + base.assert(#msg % 64 == 0) + + --local fd = io.open("/tmp/msg", "wb") + --fd:write(msg) + --fd:close() + + local chunks = #msg / 64 + + local W = { } + local start, A, B, C, D, E, f, K, TEMP + local chunk = 0 + + while chunk < chunks do + -- + -- break chunk up into W[0] through W[15] + -- + start = chunk * 64 + 1 + chunk = chunk + 1 + + for t = 0, 15 do + W[t] = from_hex(string.format("%02x%02x%02x%02x", msg:byte(start, start + 3))) + start = start + 4 + end + + -- + -- build W[16] through W[79] + -- + for t = 16, 79 do + -- For t = 16 to 79 let Wt = S1(Wt-3 XOR Wt-8 XOR Wt-14 XOR Wt-16). + W[t] = ROTATE(1, XOR(W[t-3], W[t-8], W[t-14], W[t-16])) + end + + A = H0 + B = H1 + C = H2 + D = H3 + E = H4 + + for t = 0, 79 do + if t <= 19 then + -- (B AND C) OR ((NOT B) AND D) + f = OR(AND(B, C), AND(NOT(B), D)) + K = x5A827999 + elseif t <= 39 then + -- B XOR C XOR D + f = XOR(B, C, D) + K = x6ED9EBA1 + elseif t <= 59 then + -- (B AND C) OR (B AND D) OR (C AND D + f = OR3(AND(B, C), AND(B, D), AND(C, D)) + K = x8F1BBCDC + else + -- B XOR C XOR D + f = XOR(B, C, D) + K = xCA62C1D6 + end + + -- TEMP = S5(A) + ft(B,C,D) + E + Wt + Kt; + TEMP = ADD(ROTATE(5, A), f, E, W[t], K) + + E = D + D = C + C = ROTATE(30, B) + B = A + A = TEMP + + --printf("t = %2d: %s %s %s %s %s", t, A:HEX(), B:HEX(), C:HEX(), D:HEX(), E:HEX()) + end + + -- Let H0 = H0 + A, H1 = H1 + B, H2 = H2 + C, H3 = H3 + D, H4 = H4 + E. + H0 = ADD(H0, A) + H1 = ADD(H1, B) + H2 = ADD(H2, C) + H3 = ADD(H3, D) + H4 = ADD(H4, E) + end + + return asHEX(H0) .. asHEX(H1) .. asHEX(H2) .. asHEX(H3) .. asHEX(H4) +end + +local function hex_to_binary(hex) + return hex:gsub('..', function(hexval) + return string.char(base.tonumber(hexval, 16)) + end) +end + +function sha1_binary(msg) + return hex_to_binary(sha1(msg)) +end + +local xor_with_0x5c = { + [string.char( 0)] = string.char( 92), [string.char( 1)] = string.char( 93), + [string.char( 2)] = string.char( 94), [string.char( 3)] = string.char( 95), + [string.char( 4)] = string.char( 88), [string.char( 5)] = string.char( 89), + [string.char( 6)] = string.char( 90), [string.char( 7)] = string.char( 91), + [string.char( 8)] = string.char( 84), [string.char( 9)] = string.char( 85), + [string.char( 10)] = string.char( 86), [string.char( 11)] = string.char( 87), + [string.char( 12)] = string.char( 80), [string.char( 13)] = string.char( 81), + [string.char( 14)] = string.char( 82), [string.char( 15)] = string.char( 83), + [string.char( 16)] = string.char( 76), [string.char( 17)] = string.char( 77), + [string.char( 18)] = string.char( 78), [string.char( 19)] = string.char( 79), + [string.char( 20)] = string.char( 72), [string.char( 21)] = string.char( 73), + [string.char( 22)] = string.char( 74), [string.char( 23)] = string.char( 75), + [string.char( 24)] = string.char( 68), [string.char( 25)] = string.char( 69), + [string.char( 26)] = string.char( 70), [string.char( 27)] = string.char( 71), + [string.char( 28)] = string.char( 64), [string.char( 29)] = string.char( 65), + [string.char( 30)] = string.char( 66), [string.char( 31)] = string.char( 67), + [string.char( 32)] = string.char(124), [string.char( 33)] = string.char(125), + [string.char( 34)] = string.char(126), [string.char( 35)] = string.char(127), + [string.char( 36)] = string.char(120), [string.char( 37)] = string.char(121), + [string.char( 38)] = string.char(122), [string.char( 39)] = string.char(123), + [string.char( 40)] = string.char(116), [string.char( 41)] = string.char(117), + [string.char( 42)] = string.char(118), [string.char( 43)] = string.char(119), + [string.char( 44)] = string.char(112), [string.char( 45)] = string.char(113), + [string.char( 46)] = string.char(114), [string.char( 47)] = string.char(115), + [string.char( 48)] = string.char(108), [string.char( 49)] = string.char(109), + [string.char( 50)] = string.char(110), [string.char( 51)] = string.char(111), + [string.char( 52)] = string.char(104), [string.char( 53)] = string.char(105), + [string.char( 54)] = string.char(106), [string.char( 55)] = string.char(107), + [string.char( 56)] = string.char(100), [string.char( 57)] = string.char(101), + [string.char( 58)] = string.char(102), [string.char( 59)] = string.char(103), + [string.char( 60)] = string.char( 96), [string.char( 61)] = string.char( 97), + [string.char( 62)] = string.char( 98), [string.char( 63)] = string.char( 99), + [string.char( 64)] = string.char( 28), [string.char( 65)] = string.char( 29), + [string.char( 66)] = string.char( 30), [string.char( 67)] = string.char( 31), + [string.char( 68)] = string.char( 24), [string.char( 69)] = string.char( 25), + [string.char( 70)] = string.char( 26), [string.char( 71)] = string.char( 27), + [string.char( 72)] = string.char( 20), [string.char( 73)] = string.char( 21), + [string.char( 74)] = string.char( 22), [string.char( 75)] = string.char( 23), + [string.char( 76)] = string.char( 16), [string.char( 77)] = string.char( 17), + [string.char( 78)] = string.char( 18), [string.char( 79)] = string.char( 19), + [string.char( 80)] = string.char( 12), [string.char( 81)] = string.char( 13), + [string.char( 82)] = string.char( 14), [string.char( 83)] = string.char( 15), + [string.char( 84)] = string.char( 8), [string.char( 85)] = string.char( 9), + [string.char( 86)] = string.char( 10), [string.char( 87)] = string.char( 11), + [string.char( 88)] = string.char( 4), [string.char( 89)] = string.char( 5), + [string.char( 90)] = string.char( 6), [string.char( 91)] = string.char( 7), + [string.char( 92)] = string.char( 0), [string.char( 93)] = string.char( 1), + [string.char( 94)] = string.char( 2), [string.char( 95)] = string.char( 3), + [string.char( 96)] = string.char( 60), [string.char( 97)] = string.char( 61), + [string.char( 98)] = string.char( 62), [string.char( 99)] = string.char( 63), + [string.char(100)] = string.char( 56), [string.char(101)] = string.char( 57), + [string.char(102)] = string.char( 58), [string.char(103)] = string.char( 59), + [string.char(104)] = string.char( 52), [string.char(105)] = string.char( 53), + [string.char(106)] = string.char( 54), [string.char(107)] = string.char( 55), + [string.char(108)] = string.char( 48), [string.char(109)] = string.char( 49), + [string.char(110)] = string.char( 50), [string.char(111)] = string.char( 51), + [string.char(112)] = string.char( 44), [string.char(113)] = string.char( 45), + [string.char(114)] = string.char( 46), [string.char(115)] = string.char( 47), + [string.char(116)] = string.char( 40), [string.char(117)] = string.char( 41), + [string.char(118)] = string.char( 42), [string.char(119)] = string.char( 43), + [string.char(120)] = string.char( 36), [string.char(121)] = string.char( 37), + [string.char(122)] = string.char( 38), [string.char(123)] = string.char( 39), + [string.char(124)] = string.char( 32), [string.char(125)] = string.char( 33), + [string.char(126)] = string.char( 34), [string.char(127)] = string.char( 35), + [string.char(128)] = string.char(220), [string.char(129)] = string.char(221), + [string.char(130)] = string.char(222), [string.char(131)] = string.char(223), + [string.char(132)] = string.char(216), [string.char(133)] = string.char(217), + [string.char(134)] = string.char(218), [string.char(135)] = string.char(219), + [string.char(136)] = string.char(212), [string.char(137)] = string.char(213), + [string.char(138)] = string.char(214), [string.char(139)] = string.char(215), + [string.char(140)] = string.char(208), [string.char(141)] = string.char(209), + [string.char(142)] = string.char(210), [string.char(143)] = string.char(211), + [string.char(144)] = string.char(204), [string.char(145)] = string.char(205), + [string.char(146)] = string.char(206), [string.char(147)] = string.char(207), + [string.char(148)] = string.char(200), [string.char(149)] = string.char(201), + [string.char(150)] = string.char(202), [string.char(151)] = string.char(203), + [string.char(152)] = string.char(196), [string.char(153)] = string.char(197), + [string.char(154)] = string.char(198), [string.char(155)] = string.char(199), + [string.char(156)] = string.char(192), [string.char(157)] = string.char(193), + [string.char(158)] = string.char(194), [string.char(159)] = string.char(195), + [string.char(160)] = string.char(252), [string.char(161)] = string.char(253), + [string.char(162)] = string.char(254), [string.char(163)] = string.char(255), + [string.char(164)] = string.char(248), [string.char(165)] = string.char(249), + [string.char(166)] = string.char(250), [string.char(167)] = string.char(251), + [string.char(168)] = string.char(244), [string.char(169)] = string.char(245), + [string.char(170)] = string.char(246), [string.char(171)] = string.char(247), + [string.char(172)] = string.char(240), [string.char(173)] = string.char(241), + [string.char(174)] = string.char(242), [string.char(175)] = string.char(243), + [string.char(176)] = string.char(236), [string.char(177)] = string.char(237), + [string.char(178)] = string.char(238), [string.char(179)] = string.char(239), + [string.char(180)] = string.char(232), [string.char(181)] = string.char(233), + [string.char(182)] = string.char(234), [string.char(183)] = string.char(235), + [string.char(184)] = string.char(228), [string.char(185)] = string.char(229), + [string.char(186)] = string.char(230), [string.char(187)] = string.char(231), + [string.char(188)] = string.char(224), [string.char(189)] = string.char(225), + [string.char(190)] = string.char(226), [string.char(191)] = string.char(227), + [string.char(192)] = string.char(156), [string.char(193)] = string.char(157), + [string.char(194)] = string.char(158), [string.char(195)] = string.char(159), + [string.char(196)] = string.char(152), [string.char(197)] = string.char(153), + [string.char(198)] = string.char(154), [string.char(199)] = string.char(155), + [string.char(200)] = string.char(148), [string.char(201)] = string.char(149), + [string.char(202)] = string.char(150), [string.char(203)] = string.char(151), + [string.char(204)] = string.char(144), [string.char(205)] = string.char(145), + [string.char(206)] = string.char(146), [string.char(207)] = string.char(147), + [string.char(208)] = string.char(140), [string.char(209)] = string.char(141), + [string.char(210)] = string.char(142), [string.char(211)] = string.char(143), + [string.char(212)] = string.char(136), [string.char(213)] = string.char(137), + [string.char(214)] = string.char(138), [string.char(215)] = string.char(139), + [string.char(216)] = string.char(132), [string.char(217)] = string.char(133), + [string.char(218)] = string.char(134), [string.char(219)] = string.char(135), + [string.char(220)] = string.char(128), [string.char(221)] = string.char(129), + [string.char(222)] = string.char(130), [string.char(223)] = string.char(131), + [string.char(224)] = string.char(188), [string.char(225)] = string.char(189), + [string.char(226)] = string.char(190), [string.char(227)] = string.char(191), + [string.char(228)] = string.char(184), [string.char(229)] = string.char(185), + [string.char(230)] = string.char(186), [string.char(231)] = string.char(187), + [string.char(232)] = string.char(180), [string.char(233)] = string.char(181), + [string.char(234)] = string.char(182), [string.char(235)] = string.char(183), + [string.char(236)] = string.char(176), [string.char(237)] = string.char(177), + [string.char(238)] = string.char(178), [string.char(239)] = string.char(179), + [string.char(240)] = string.char(172), [string.char(241)] = string.char(173), + [string.char(242)] = string.char(174), [string.char(243)] = string.char(175), + [string.char(244)] = string.char(168), [string.char(245)] = string.char(169), + [string.char(246)] = string.char(170), [string.char(247)] = string.char(171), + [string.char(248)] = string.char(164), [string.char(249)] = string.char(165), + [string.char(250)] = string.char(166), [string.char(251)] = string.char(167), + [string.char(252)] = string.char(160), [string.char(253)] = string.char(161), + [string.char(254)] = string.char(162), [string.char(255)] = string.char(163), +} + +local xor_with_0x36 = { + [string.char( 0)] = string.char( 54), [string.char( 1)] = string.char( 55), + [string.char( 2)] = string.char( 52), [string.char( 3)] = string.char( 53), + [string.char( 4)] = string.char( 50), [string.char( 5)] = string.char( 51), + [string.char( 6)] = string.char( 48), [string.char( 7)] = string.char( 49), + [string.char( 8)] = string.char( 62), [string.char( 9)] = string.char( 63), + [string.char( 10)] = string.char( 60), [string.char( 11)] = string.char( 61), + [string.char( 12)] = string.char( 58), [string.char( 13)] = string.char( 59), + [string.char( 14)] = string.char( 56), [string.char( 15)] = string.char( 57), + [string.char( 16)] = string.char( 38), [string.char( 17)] = string.char( 39), + [string.char( 18)] = string.char( 36), [string.char( 19)] = string.char( 37), + [string.char( 20)] = string.char( 34), [string.char( 21)] = string.char( 35), + [string.char( 22)] = string.char( 32), [string.char( 23)] = string.char( 33), + [string.char( 24)] = string.char( 46), [string.char( 25)] = string.char( 47), + [string.char( 26)] = string.char( 44), [string.char( 27)] = string.char( 45), + [string.char( 28)] = string.char( 42), [string.char( 29)] = string.char( 43), + [string.char( 30)] = string.char( 40), [string.char( 31)] = string.char( 41), + [string.char( 32)] = string.char( 22), [string.char( 33)] = string.char( 23), + [string.char( 34)] = string.char( 20), [string.char( 35)] = string.char( 21), + [string.char( 36)] = string.char( 18), [string.char( 37)] = string.char( 19), + [string.char( 38)] = string.char( 16), [string.char( 39)] = string.char( 17), + [string.char( 40)] = string.char( 30), [string.char( 41)] = string.char( 31), + [string.char( 42)] = string.char( 28), [string.char( 43)] = string.char( 29), + [string.char( 44)] = string.char( 26), [string.char( 45)] = string.char( 27), + [string.char( 46)] = string.char( 24), [string.char( 47)] = string.char( 25), + [string.char( 48)] = string.char( 6), [string.char( 49)] = string.char( 7), + [string.char( 50)] = string.char( 4), [string.char( 51)] = string.char( 5), + [string.char( 52)] = string.char( 2), [string.char( 53)] = string.char( 3), + [string.char( 54)] = string.char( 0), [string.char( 55)] = string.char( 1), + [string.char( 56)] = string.char( 14), [string.char( 57)] = string.char( 15), + [string.char( 58)] = string.char( 12), [string.char( 59)] = string.char( 13), + [string.char( 60)] = string.char( 10), [string.char( 61)] = string.char( 11), + [string.char( 62)] = string.char( 8), [string.char( 63)] = string.char( 9), + [string.char( 64)] = string.char(118), [string.char( 65)] = string.char(119), + [string.char( 66)] = string.char(116), [string.char( 67)] = string.char(117), + [string.char( 68)] = string.char(114), [string.char( 69)] = string.char(115), + [string.char( 70)] = string.char(112), [string.char( 71)] = string.char(113), + [string.char( 72)] = string.char(126), [string.char( 73)] = string.char(127), + [string.char( 74)] = string.char(124), [string.char( 75)] = string.char(125), + [string.char( 76)] = string.char(122), [string.char( 77)] = string.char(123), + [string.char( 78)] = string.char(120), [string.char( 79)] = string.char(121), + [string.char( 80)] = string.char(102), [string.char( 81)] = string.char(103), + [string.char( 82)] = string.char(100), [string.char( 83)] = string.char(101), + [string.char( 84)] = string.char( 98), [string.char( 85)] = string.char( 99), + [string.char( 86)] = string.char( 96), [string.char( 87)] = string.char( 97), + [string.char( 88)] = string.char(110), [string.char( 89)] = string.char(111), + [string.char( 90)] = string.char(108), [string.char( 91)] = string.char(109), + [string.char( 92)] = string.char(106), [string.char( 93)] = string.char(107), + [string.char( 94)] = string.char(104), [string.char( 95)] = string.char(105), + [string.char( 96)] = string.char( 86), [string.char( 97)] = string.char( 87), + [string.char( 98)] = string.char( 84), [string.char( 99)] = string.char( 85), + [string.char(100)] = string.char( 82), [string.char(101)] = string.char( 83), + [string.char(102)] = string.char( 80), [string.char(103)] = string.char( 81), + [string.char(104)] = string.char( 94), [string.char(105)] = string.char( 95), + [string.char(106)] = string.char( 92), [string.char(107)] = string.char( 93), + [string.char(108)] = string.char( 90), [string.char(109)] = string.char( 91), + [string.char(110)] = string.char( 88), [string.char(111)] = string.char( 89), + [string.char(112)] = string.char( 70), [string.char(113)] = string.char( 71), + [string.char(114)] = string.char( 68), [string.char(115)] = string.char( 69), + [string.char(116)] = string.char( 66), [string.char(117)] = string.char( 67), + [string.char(118)] = string.char( 64), [string.char(119)] = string.char( 65), + [string.char(120)] = string.char( 78), [string.char(121)] = string.char( 79), + [string.char(122)] = string.char( 76), [string.char(123)] = string.char( 77), + [string.char(124)] = string.char( 74), [string.char(125)] = string.char( 75), + [string.char(126)] = string.char( 72), [string.char(127)] = string.char( 73), + [string.char(128)] = string.char(182), [string.char(129)] = string.char(183), + [string.char(130)] = string.char(180), [string.char(131)] = string.char(181), + [string.char(132)] = string.char(178), [string.char(133)] = string.char(179), + [string.char(134)] = string.char(176), [string.char(135)] = string.char(177), + [string.char(136)] = string.char(190), [string.char(137)] = string.char(191), + [string.char(138)] = string.char(188), [string.char(139)] = string.char(189), + [string.char(140)] = string.char(186), [string.char(141)] = string.char(187), + [string.char(142)] = string.char(184), [string.char(143)] = string.char(185), + [string.char(144)] = string.char(166), [string.char(145)] = string.char(167), + [string.char(146)] = string.char(164), [string.char(147)] = string.char(165), + [string.char(148)] = string.char(162), [string.char(149)] = string.char(163), + [string.char(150)] = string.char(160), [string.char(151)] = string.char(161), + [string.char(152)] = string.char(174), [string.char(153)] = string.char(175), + [string.char(154)] = string.char(172), [string.char(155)] = string.char(173), + [string.char(156)] = string.char(170), [string.char(157)] = string.char(171), + [string.char(158)] = string.char(168), [string.char(159)] = string.char(169), + [string.char(160)] = string.char(150), [string.char(161)] = string.char(151), + [string.char(162)] = string.char(148), [string.char(163)] = string.char(149), + [string.char(164)] = string.char(146), [string.char(165)] = string.char(147), + [string.char(166)] = string.char(144), [string.char(167)] = string.char(145), + [string.char(168)] = string.char(158), [string.char(169)] = string.char(159), + [string.char(170)] = string.char(156), [string.char(171)] = string.char(157), + [string.char(172)] = string.char(154), [string.char(173)] = string.char(155), + [string.char(174)] = string.char(152), [string.char(175)] = string.char(153), + [string.char(176)] = string.char(134), [string.char(177)] = string.char(135), + [string.char(178)] = string.char(132), [string.char(179)] = string.char(133), + [string.char(180)] = string.char(130), [string.char(181)] = string.char(131), + [string.char(182)] = string.char(128), [string.char(183)] = string.char(129), + [string.char(184)] = string.char(142), [string.char(185)] = string.char(143), + [string.char(186)] = string.char(140), [string.char(187)] = string.char(141), + [string.char(188)] = string.char(138), [string.char(189)] = string.char(139), + [string.char(190)] = string.char(136), [string.char(191)] = string.char(137), + [string.char(192)] = string.char(246), [string.char(193)] = string.char(247), + [string.char(194)] = string.char(244), [string.char(195)] = string.char(245), + [string.char(196)] = string.char(242), [string.char(197)] = string.char(243), + [string.char(198)] = string.char(240), [string.char(199)] = string.char(241), + [string.char(200)] = string.char(254), [string.char(201)] = string.char(255), + [string.char(202)] = string.char(252), [string.char(203)] = string.char(253), + [string.char(204)] = string.char(250), [string.char(205)] = string.char(251), + [string.char(206)] = string.char(248), [string.char(207)] = string.char(249), + [string.char(208)] = string.char(230), [string.char(209)] = string.char(231), + [string.char(210)] = string.char(228), [string.char(211)] = string.char(229), + [string.char(212)] = string.char(226), [string.char(213)] = string.char(227), + [string.char(214)] = string.char(224), [string.char(215)] = string.char(225), + [string.char(216)] = string.char(238), [string.char(217)] = string.char(239), + [string.char(218)] = string.char(236), [string.char(219)] = string.char(237), + [string.char(220)] = string.char(234), [string.char(221)] = string.char(235), + [string.char(222)] = string.char(232), [string.char(223)] = string.char(233), + [string.char(224)] = string.char(214), [string.char(225)] = string.char(215), + [string.char(226)] = string.char(212), [string.char(227)] = string.char(213), + [string.char(228)] = string.char(210), [string.char(229)] = string.char(211), + [string.char(230)] = string.char(208), [string.char(231)] = string.char(209), + [string.char(232)] = string.char(222), [string.char(233)] = string.char(223), + [string.char(234)] = string.char(220), [string.char(235)] = string.char(221), + [string.char(236)] = string.char(218), [string.char(237)] = string.char(219), + [string.char(238)] = string.char(216), [string.char(239)] = string.char(217), + [string.char(240)] = string.char(198), [string.char(241)] = string.char(199), + [string.char(242)] = string.char(196), [string.char(243)] = string.char(197), + [string.char(244)] = string.char(194), [string.char(245)] = string.char(195), + [string.char(246)] = string.char(192), [string.char(247)] = string.char(193), + [string.char(248)] = string.char(206), [string.char(249)] = string.char(207), + [string.char(250)] = string.char(204), [string.char(251)] = string.char(205), + [string.char(252)] = string.char(202), [string.char(253)] = string.char(203), + [string.char(254)] = string.char(200), [string.char(255)] = string.char(201), +} + + +local blocksize = 64 -- 512 bits + +function hmac_sha1(key, text) + base.assert(base.type(key) == 'string', "key passed to hmac_sha1 should be a string") + base.assert(base.type(text) == 'string', "text passed to hmac_sha1 should be a string") + + if #key > blocksize then + key = sha1_binary(key) + end + + local key_xord_with_0x36 = key:gsub('.', xor_with_0x36) .. string.rep(string.char(0x36), blocksize - #key) + local key_xord_with_0x5c = key:gsub('.', xor_with_0x5c) .. string.rep(string.char(0x5c), blocksize - #key) + + return sha1(key_xord_with_0x5c .. sha1_binary(key_xord_with_0x36 .. text)) +end + +function hmac_sha1_binary(key, text) + return hex_to_binary(hmac_sha1(key, text)) +end \ No newline at end of file diff --git a/Mi_Lua/slaxdom.lua b/Mi_Lua/slaxdom.lua new file mode 100644 index 0000000..a1c96f7 --- /dev/null +++ b/Mi_Lua/slaxdom.lua @@ -0,0 +1,53 @@ +local SLAXML = require "slaxml" +local string = require "string" +local table = require "table" + +module "slaxdom" + +function SLAXML:dom(xml,opts) + if not opts then opts={} end + local rich = not opts.simple + local push, pop = table.insert, table.remove + local stack = {} + local doc = { type="document", name="#doc", kids={} } + local current = doc + local builder = SLAXML:parser{ + startElement = function(name,nsURI) + local el = { type="element", name=name, kids={}, el=rich and {} or nil, attr={}, nsURI=nsURI, parent=rich and current or nil } + if current==doc then + if doc.root then error(("Encountered element '%s' when the document already has a root '%s' element"):format(name,doc.root.name)) end + doc.root = el + end + push(current.kids,el) + if current.el then push(current.el,el) end + current = el + push(stack,el) + end, + attribute = function(name,value,nsURI) + if not current or current.type~="element" then error(("Encountered an attribute %s=%s but I wasn't inside an element"):format(name,value)) end + local attr = {type='attribute',name=name,nsURI=nsURI,value=value,parent=rich and current or nil} + if rich then current.attr[name] = value end + push(current.attr,attr) + end, + closeElement = function(name) + if current.name~=name or current.type~="element" then error(("Received a close element notification for '%s' but was inside a '%s' %s"):format(name,current.name,current.type)) end + pop(stack) + current = stack[#stack] + end, + text = function(value) + if current.type~='document' then + if current.type~="element" then error(("Received a text notification '%s' but was inside a %s"):format(value,current.type)) end + push(current.kids,{type='text',name='#text',value=value,parent=rich and current or nil}) + end + end, + comment = function(value) + push(current.kids,{type='comment',name='#comment',value=value,parent=rich and current or nil}) + end, + pi = function(name,value) + push(current.kids,{type='pi',name=name,value=value,parent=rich and current or nil}) + end + } + builder:parse(xml,opts) + return doc +end +return SLAXML diff --git a/Mi_Lua/slaxml.lua b/Mi_Lua/slaxml.lua new file mode 100644 index 0000000..7bc8c27 --- /dev/null +++ b/Mi_Lua/slaxml.lua @@ -0,0 +1,221 @@ +local string = require "string" +local table = require "table" +local base = _G + +module "slaxml" + +local SLAXML = { + VERSION = "0.5.1", + _call = { + pi = function(target,content) + print(string.format("",target,content)) + end, + comment = function(content) + print(string.format("",content)) + end, + startElement = function(name,nsURI) + print(string.format("<%s%s>",name,nsURI and (" ("..nsURI..")") or "")) + end, + attribute = function(name,value,nsURI) + print(string.format(" %s=%q%s",name,value,nsURI and (" ("..nsURI..")") or "")) + end, + text = function(text) + print(string.format(" text: %q",text)) + end, + closeElement = function(name,nsURI) + print(string.format("",name)) + end, + } +} + +function SLAXML:parser(callbacks) + return { _call=callbacks or self._call, parse=SLAXML.parse } +end + +function SLAXML:parse(xml,options) + if not options then options = { stripWhitespace=false } end + + -- Cache references for maximum speed + local find, sub, gsub, char, push, pop = string.find, string.sub, string.gsub, string.char, table.insert, table.remove + local first, last, match1, match2, match3, pos2, nsURI + local pos = 1 + local state = "text" + local textStart = 1 + local currentElement={} + local currentAttributes={} + local currentAttributeCt + local nsStack = {} + + local entityMap = { ["lt"]="<", ["gt"]=">", ["amp"]="&", ["quot"]='"', ["apos"]="'" } + local entitySwap = function(orig,n,s) return entityMap[s] or n=="#" and char(s) or orig end + local function unescape(str) return gsub( str, '(&(#?)([%d%a]+);)', entitySwap ) end + + local function finishText() + if first>textStart and self._call.text then + local text = sub(xml,textStart,first-1) + if options.stripWhitespace then + text = gsub(text,'^%s+','') + text = gsub(text,'%s+$','') + if #text==0 then text=nil end + end + if text then self._call.text(unescape(text)) end + end + end + + local function findPI() + first, last, match1, match2 = find( xml, '^<%?([:%a_][:%w_.-]*) ?(.-)%?>', pos ) + if first then + finishText() + if self._call.pi then self._call.pi(match1,match2) end + pos = last+1 + textStart = pos + return true + end + end + + local function findComment() + first, last, match1 = find( xml, '^', pos ) + if first then + finishText() + if self._call.comment then self._call.comment(match1) end + pos = last+1 + textStart = pos + return true + end + end + + local function nsForPrefix(prefix) + for i=#nsStack,1,-1 do if nsStack[i][prefix] then return nsStack[i][prefix] end end + error(("Cannot find namespace for prefix %s"):format(prefix)) + end + + local function startElement() + first, last, match1 = find( xml, '^<([%a_][%w_.-]*)', pos ) + if first then + currentElement[2] = nil + finishText() + pos = last+1 + first,last,match2 = find(xml, '^:([%a_][%w_.-]*)', pos ) + if first then + currentElement[1] = match2 + currentElement[2] = nsForPrefix(match1) + match1 = match2 + pos = last+1 + else + currentElement[1] = match1 + for i=#nsStack,1,-1 do if nsStack[i]['!'] then currentElement[2] = nsStack[i]['!']; break end end + end + currentAttributeCt = 0 + push(nsStack,{}) + return true + end + end + + local function findAttribute() + first, last, match1 = find( xml, '^%s+([:%a_][:%w_.-]*)%s*=%s*', pos ) + if first then + pos2 = last+1 + first, last, match2 = find( xml, '^"([^<"]*)"', pos2 ) -- FIXME: disallow non-entity ampersands + if first then + pos = last+1 + match2 = unescape(match2) + else + first, last, match2 = find( xml, "^'([^<']*)'", pos2 ) -- FIXME: disallow non-entity ampersands + if first then + pos = last+1 + match2 = unescape(match2) + end + end + end + if match1 and match2 then + local currentAttribute = {match1,match2} + local prefix,name = string.match(match1,'^([^:]+):([^:]+)$') + if prefix then + if prefix=='xmlns' then + nsStack[#nsStack][name] = match2 + else + currentAttribute[1] = name + currentAttribute[3] = nsForPrefix(prefix) + end + else + if match1=='xmlns' then + nsStack[#nsStack]['!'] = match2 + currentElement[2] = match2 + end + end + currentAttributeCt = currentAttributeCt + 1 + currentAttributes[currentAttributeCt] = currentAttribute + return true + end + end + + local function findCDATA() + first, last, match1 = find( xml, '^', pos ) + if first then + finishText() + if self._call.text then self._call.text(match1) end + pos = last+1 + textStart = pos + return true + end + end + + local function closeElement() + first, last, match1 = find( xml, '^%s*(/?)>', pos ) + if first then + state = "text" + pos = last+1 + textStart = pos + + if self._call.startElement then self._call.startElement(base.unpack(currentElement)) end + if self._call.attribute then + for i=1,currentAttributeCt do self._call.attribute(base.unpack(currentAttributes[i])) end end + + if match1=="/" then + pop(nsStack) + if self._call.closeElement then self._call.closeElement(base.unpack(currentElement)) end + end + return true + end + end + + local function findElementClose() + first, last, match1, match2 = find( xml, '^', pos ) + if first then + nsURI = nil + for i=#nsStack,1,-1 do if nsStack[i]['!'] then nsURI = nsStack[i]['!']; break end end + else + first, last, match2, match1 = find( xml, '^', pos ) + if first then nsURI = nsForPrefix(match2) end + end + if first then + finishText() + if self._call.closeElement then self._call.closeElement(match1,nsURI) end + pos = last+1 + textStart = pos + pop(nsStack) + return true + end + end + + while pos<#xml do + if state=="text" then + if not (findPI() or findComment() or findCDATA() or findElementClose()) then + if startElement() then + state = "attributes" + else + first, last = find( xml, '^[^<]+', pos ) + pos = (first and last or pos) + 1 + end + end + elseif state=="attributes" then + if not findAttribute() then + if not closeElement() then + error("Was in an element and couldn't find attributes or the close.") + end + end + end + end +end + +return SLAXML diff --git a/Mi_Lua/socket.lua b/Mi_Lua/socket.lua new file mode 100644 index 0000000..211adcd --- /dev/null +++ b/Mi_Lua/socket.lua @@ -0,0 +1,133 @@ +----------------------------------------------------------------------------- +-- LuaSocket helper module +-- Author: Diego Nehab +-- RCS ID: $Id: socket.lua,v 1.22 2005/11/22 08:33:29 diego Exp $ +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Declare module and import dependencies +----------------------------------------------------------------------------- +local base = _G +local string = require("string") +local math = require("math") +local socket = require("socket.core") +module("socket") + +----------------------------------------------------------------------------- +-- Exported auxiliar functions +----------------------------------------------------------------------------- +function connect(address, port, laddress, lport) + local sock, err = socket.tcp() + if not sock then return nil, err end + if laddress then + local res, err = sock:bind(laddress, lport, -1) + if not res then return nil, err end + end + local res, err = sock:connect(address, port) + if not res then return nil, err end + return sock +end + +function bind(host, port, backlog) + local sock, err = socket.tcp() + if not sock then return nil, err end + sock:setoption("reuseaddr", true) + local res, err = sock:bind(host, port) + if not res then return nil, err end + res, err = sock:listen(backlog) + if not res then return nil, err end + return sock +end + +try = newtry() + +function choose(table) + return function(name, opt1, opt2) + if base.type(name) ~= "string" then + name, opt1, opt2 = "default", name, opt1 + end + local f = table[name or "nil"] + if not f then base.error("unknown key (".. base.tostring(name) ..")", 3) + else return f(opt1, opt2) end + end +end + +----------------------------------------------------------------------------- +-- Socket sources and sinks, conforming to LTN12 +----------------------------------------------------------------------------- +-- create namespaces inside LuaSocket namespace +sourcet = {} +sinkt = {} + +BLOCKSIZE = 2048 + +sinkt["close-when-done"] = function(sock) + return base.setmetatable({ + getfd = function() return sock:getfd() end, + dirty = function() return sock:dirty() end + }, { + __call = function(self, chunk, err) + if not chunk then + sock:close() + return 1 + else return sock:send(chunk) end + end + }) +end + +sinkt["keep-open"] = function(sock) + return base.setmetatable({ + getfd = function() return sock:getfd() end, + dirty = function() return sock:dirty() end + }, { + __call = function(self, chunk, err) + if chunk then return sock:send(chunk) + else return 1 end + end + }) +end + +sinkt["default"] = sinkt["keep-open"] + +sink = choose(sinkt) + +sourcet["by-length"] = function(sock, length) + return base.setmetatable({ + getfd = function() return sock:getfd() end, + dirty = function() return sock:dirty() end + }, { + __call = function() + if length <= 0 then return nil end + local size = math.min(socket.BLOCKSIZE, length) + local chunk, err = sock:receive(size) + if err then return nil, err end + length = length - string.len(chunk) + return chunk + end + }) +end + +sourcet["until-closed"] = function(sock) + local done + return base.setmetatable({ + getfd = function() return sock:getfd() end, + dirty = function() return sock:dirty() end + }, { + __call = function() + if done then return nil end + local chunk, err, partial = sock:receive(socket.BLOCKSIZE) + if not err then return chunk + elseif err == "closed" then + sock:close() + done = 1 + return partial + else return nil, err end + end + }) +end + + +sourcet["default"] = sourcet["until-closed"] + +source = choose(sourcet) + diff --git a/Mi_Lua/socket.so.2.0.2 b/Mi_Lua/socket.so.2.0.2 new file mode 100644 index 0000000000000000000000000000000000000000..f27b8366c74e563f1705d3deaaa451ecf512ba5f GIT binary patch literal 35436 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 literal 0 HcmV?d00001 diff --git a/Mi_Lua/socket/ftp.lua b/Mi_Lua/socket/ftp.lua new file mode 100644 index 0000000..598f65d --- /dev/null +++ b/Mi_Lua/socket/ftp.lua @@ -0,0 +1,281 @@ +----------------------------------------------------------------------------- +-- FTP support for the Lua language +-- LuaSocket toolkit. +-- Author: Diego Nehab +-- RCS ID: $Id: ftp.lua,v 1.45 2007/07/11 19:25:47 diego Exp $ +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Declare module and import dependencies +----------------------------------------------------------------------------- +local base = _G +local table = require("table") +local string = require("string") +local math = require("math") +local socket = require("socket") +local url = require("socket.url") +local tp = require("socket.tp") +local ltn12 = require("ltn12") +module("socket.ftp") + +----------------------------------------------------------------------------- +-- Program constants +----------------------------------------------------------------------------- +-- timeout in seconds before the program gives up on a connection +TIMEOUT = 60 +-- default port for ftp service +PORT = 21 +-- this is the default anonymous password. used when no password is +-- provided in url. should be changed to your e-mail. +USER = "ftp" +PASSWORD = "anonymous@anonymous.org" + +----------------------------------------------------------------------------- +-- Low level FTP API +----------------------------------------------------------------------------- +local metat = { __index = {} } + +function open(server, port, create) + local tp = socket.try(tp.connect(server, port or PORT, TIMEOUT, create)) + local f = base.setmetatable({ tp = tp }, metat) + -- make sure everything gets closed in an exception + f.try = socket.newtry(function() f:close() end) + return f +end + +function metat.__index:portconnect() + self.try(self.server:settimeout(TIMEOUT)) + self.data = self.try(self.server:accept()) + self.try(self.data:settimeout(TIMEOUT)) +end + +function metat.__index:pasvconnect() + self.data = self.try(socket.tcp()) + self.try(self.data:settimeout(TIMEOUT)) + self.try(self.data:connect(self.pasvt.ip, self.pasvt.port)) +end + +function metat.__index:login(user, password) + self.try(self.tp:command("user", user or USER)) + local code, reply = self.try(self.tp:check{"2..", 331}) + if code == 331 then + self.try(self.tp:command("pass", password or PASSWORD)) + self.try(self.tp:check("2..")) + end + return 1 +end + +function metat.__index:pasv() + self.try(self.tp:command("pasv")) + local code, reply = self.try(self.tp:check("2..")) + local pattern = "(%d+)%D(%d+)%D(%d+)%D(%d+)%D(%d+)%D(%d+)" + local a, b, c, d, p1, p2 = socket.skip(2, string.find(reply, pattern)) + self.try(a and b and c and d and p1 and p2, reply) + self.pasvt = { + ip = string.format("%d.%d.%d.%d", a, b, c, d), + port = p1*256 + p2 + } + if self.server then + self.server:close() + self.server = nil + end + return self.pasvt.ip, self.pasvt.port +end + +function metat.__index:port(ip, port) + self.pasvt = nil + if not ip then + ip, port = self.try(self.tp:getcontrol():getsockname()) + self.server = self.try(socket.bind(ip, 0)) + ip, port = self.try(self.server:getsockname()) + self.try(self.server:settimeout(TIMEOUT)) + end + local pl = math.mod(port, 256) + local ph = (port - pl)/256 + local arg = string.gsub(string.format("%s,%d,%d", ip, ph, pl), "%.", ",") + self.try(self.tp:command("port", arg)) + self.try(self.tp:check("2..")) + return 1 +end + +function metat.__index:send(sendt) + self.try(self.pasvt or self.server, "need port or pasv first") + -- if there is a pasvt table, we already sent a PASV command + -- we just get the data connection into self.data + if self.pasvt then self:pasvconnect() end + -- get the transfer argument and command + local argument = sendt.argument or + url.unescape(string.gsub(sendt.path or "", "^[/\\]", "")) + if argument == "" then argument = nil end + local command = sendt.command or "stor" + -- send the transfer command and check the reply + self.try(self.tp:command(command, argument)) + local code, reply = self.try(self.tp:check{"2..", "1.."}) + -- if there is not a a pasvt table, then there is a server + -- and we already sent a PORT command + if not self.pasvt then self:portconnect() end + -- get the sink, source and step for the transfer + local step = sendt.step or ltn12.pump.step + local readt = {self.tp.c} + local checkstep = function(src, snk) + -- check status in control connection while downloading + local readyt = socket.select(readt, nil, 0) + if readyt[tp] then code = self.try(self.tp:check("2..")) end + return step(src, snk) + end + local sink = socket.sink("close-when-done", self.data) + -- transfer all data and check error + self.try(ltn12.pump.all(sendt.source, sink, checkstep)) + if string.find(code, "1..") then self.try(self.tp:check("2..")) end + -- done with data connection + self.data:close() + -- find out how many bytes were sent + local sent = socket.skip(1, self.data:getstats()) + self.data = nil + return sent +end + +function metat.__index:receive(recvt) + self.try(self.pasvt or self.server, "need port or pasv first") + if self.pasvt then self:pasvconnect() end + local argument = recvt.argument or + url.unescape(string.gsub(recvt.path or "", "^[/\\]", "")) + if argument == "" then argument = nil end + local command = recvt.command or "retr" + self.try(self.tp:command(command, argument)) + local code = self.try(self.tp:check{"1..", "2.."}) + if not self.pasvt then self:portconnect() end + local source = socket.source("until-closed", self.data) + local step = recvt.step or ltn12.pump.step + self.try(ltn12.pump.all(source, recvt.sink, step)) + if string.find(code, "1..") then self.try(self.tp:check("2..")) end + self.data:close() + self.data = nil + return 1 +end + +function metat.__index:cwd(dir) + self.try(self.tp:command("cwd", dir)) + self.try(self.tp:check(250)) + return 1 +end + +function metat.__index:type(type) + self.try(self.tp:command("type", type)) + self.try(self.tp:check(200)) + return 1 +end + +function metat.__index:greet() + local code = self.try(self.tp:check{"1..", "2.."}) + if string.find(code, "1..") then self.try(self.tp:check("2..")) end + return 1 +end + +function metat.__index:quit() + self.try(self.tp:command("quit")) + self.try(self.tp:check("2..")) + return 1 +end + +function metat.__index:close() + if self.data then self.data:close() end + if self.server then self.server:close() end + return self.tp:close() +end + +----------------------------------------------------------------------------- +-- High level FTP API +----------------------------------------------------------------------------- +local function override(t) + if t.url then + local u = url.parse(t.url) + for i,v in base.pairs(t) do + u[i] = v + end + return u + else return t end +end + +local function tput(putt) + putt = override(putt) + socket.try(putt.host, "missing hostname") + local f = open(putt.host, putt.port, putt.create) + f:greet() + f:login(putt.user, putt.password) + if putt.type then f:type(putt.type) end + f:pasv() + local sent = f:send(putt) + f:quit() + f:close() + return sent +end + +local default = { + path = "/", + scheme = "ftp" +} + +local function parse(u) + local t = socket.try(url.parse(u, default)) + socket.try(t.scheme == "ftp", "wrong scheme '" .. t.scheme .. "'") + socket.try(t.host, "missing hostname") + local pat = "^type=(.)$" + if t.params then + t.type = socket.skip(2, string.find(t.params, pat)) + socket.try(t.type == "a" or t.type == "i", + "invalid type '" .. t.type .. "'") + end + return t +end + +local function sput(u, body) + local putt = parse(u) + putt.source = ltn12.source.string(body) + return tput(putt) +end + +put = socket.protect(function(putt, body) + if base.type(putt) == "string" then return sput(putt, body) + else return tput(putt) end +end) + +local function tget(gett) + gett = override(gett) + socket.try(gett.host, "missing hostname") + local f = open(gett.host, gett.port, gett.create) + f:greet() + f:login(gett.user, gett.password) + if gett.type then f:type(gett.type) end + f:pasv() + f:receive(gett) + f:quit() + return f:close() +end + +local function sget(u) + local gett = parse(u) + local t = {} + gett.sink = ltn12.sink.table(t) + tget(gett) + return table.concat(t) +end + +command = socket.protect(function(cmdt) + cmdt = override(cmdt) + socket.try(cmdt.host, "missing hostname") + socket.try(cmdt.command, "missing command") + local f = open(cmdt.host, cmdt.port, cmdt.create) + f:greet() + f:login(cmdt.user, cmdt.password) + f.try(f.tp:command(cmdt.command, cmdt.argument)) + if cmdt.check then f.try(f.tp:check(cmdt.check)) end + f:quit() + return f:close() +end) + +get = socket.protect(function(gett) + if base.type(gett) == "string" then return sget(gett) + else return tget(gett) end +end) + diff --git a/Mi_Lua/socket/http.lua b/Mi_Lua/socket/http.lua new file mode 100644 index 0000000..ad8db1e --- /dev/null +++ b/Mi_Lua/socket/http.lua @@ -0,0 +1,350 @@ +----------------------------------------------------------------------------- +-- HTTP/1.1 client support for the Lua language. +-- LuaSocket toolkit. +-- Author: Diego Nehab +-- RCS ID: $Id: http.lua,v 1.71 2007/10/13 23:55:20 diego Exp $ +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Declare module and import dependencies +------------------------------------------------------------------------------- +local socket = require("socket") +local url = require("socket.url") +local ltn12 = require("ltn12") +local mime = require("mime") +local string = require("string") +local base = _G +local table = require("table") +module("socket.http") + +----------------------------------------------------------------------------- +-- Program constants +----------------------------------------------------------------------------- +-- connection timeout in seconds +TIMEOUT = 60 +-- default port for document retrieval +PORT = 80 +-- user agent field sent in request +USERAGENT = socket._VERSION + +----------------------------------------------------------------------------- +-- Reads MIME headers from a connection, unfolding where needed +----------------------------------------------------------------------------- +local function receiveheaders(sock, headers) + local line, name, value, err + headers = headers or {} + -- get first line + line, err = sock:receive() + if err then return nil, err end + -- headers go until a blank line is found + while line ~= "" do + -- get field-name and value + name, value = socket.skip(2, string.find(line, "^(.-):%s*(.*)")) + if not (name and value) then return nil, "malformed reponse headers" end + name = string.lower(name) + -- get next line (value might be folded) + line, err = sock:receive() + if err then return nil, err end + -- unfold any folded values + while string.find(line, "^%s") do + value = value .. line + line = sock:receive() + if err then return nil, err end + end + -- save pair in table + if headers[name] then headers[name] = headers[name] .. ", " .. value + else headers[name] = value end + end + return headers +end + +----------------------------------------------------------------------------- +-- Extra sources and sinks +----------------------------------------------------------------------------- +socket.sourcet["http-chunked"] = function(sock, headers) + return base.setmetatable({ + getfd = function() return sock:getfd() end, + dirty = function() return sock:dirty() end + }, { + __call = function() + -- get chunk size, skip extention + local line, err = sock:receive() + if err then return nil, err end + local size = base.tonumber(string.gsub(line, ";.*", ""), 16) + if not size then return nil, "invalid chunk size" end + -- was it the last chunk? + if size > 0 then + -- if not, get chunk and skip terminating CRLF + local chunk, err, part = sock:receive(size) + if chunk then sock:receive() end + return chunk, err + else + -- if it was, read trailers into headers table + headers, err = receiveheaders(sock, headers) + if not headers then return nil, err end + end + end + }) +end + +socket.sinkt["http-chunked"] = function(sock) + return base.setmetatable({ + getfd = function() return sock:getfd() end, + dirty = function() return sock:dirty() end + }, { + __call = function(self, chunk, err) + if not chunk then return sock:send("0\r\n\r\n") end + local size = string.format("%X\r\n", string.len(chunk)) + return sock:send(size .. chunk .. "\r\n") + end + }) +end + +----------------------------------------------------------------------------- +-- Low level HTTP API +----------------------------------------------------------------------------- +local metat = { __index = {} } + +function open(host, port, create) + -- create socket with user connect function, or with default + local c = socket.try((create or socket.tcp)()) + local h = base.setmetatable({ c = c }, metat) + -- create finalized try + h.try = socket.newtry(function() h:close() end) + -- set timeout before connecting + h.try(c:settimeout(TIMEOUT)) + h.try(c:connect(host, port or PORT)) + -- here everything worked + return h +end + +function metat.__index:sendrequestline(method, uri) + local reqline = string.format("%s %s HTTP/1.1\r\n", method or "GET", uri) + return self.try(self.c:send(reqline)) +end + +function metat.__index:sendheaders(headers) + local h = "\r\n" + for i, v in base.pairs(headers) do + h = i .. ": " .. v .. "\r\n" .. h + end + self.try(self.c:send(h)) + return 1 +end + +function metat.__index:sendbody(headers, source, step) + source = source or ltn12.source.empty() + step = step or ltn12.pump.step + -- if we don't know the size in advance, send chunked and hope for the best + local mode = "http-chunked" + if headers["content-length"] then mode = "keep-open" end + return self.try(ltn12.pump.all(source, socket.sink(mode, self.c), step)) +end + +function metat.__index:receivestatusline() + local status = self.try(self.c:receive(5)) + -- identify HTTP/0.9 responses, which do not contain a status line + -- this is just a heuristic, but is what the RFC recommends + if status ~= "HTTP/" then return nil, status end + -- otherwise proceed reading a status line + status = self.try(self.c:receive("*l", status)) + local code = socket.skip(2, string.find(status, "HTTP/%d*%.%d* (%d%d%d)")) + return self.try(base.tonumber(code), status) +end + +function metat.__index:receiveheaders() + return self.try(receiveheaders(self.c)) +end + +function metat.__index:receivebody(headers, sink, step) + sink = sink or ltn12.sink.null() + step = step or ltn12.pump.step + local length = base.tonumber(headers["content-length"]) + local t = headers["transfer-encoding"] -- shortcut + local mode = "default" -- connection close + if t and t ~= "identity" then mode = "http-chunked" + elseif base.tonumber(headers["content-length"]) then mode = "by-length" end + return self.try(ltn12.pump.all(socket.source(mode, self.c, length), + sink, step)) +end + +function metat.__index:receive09body(status, sink, step) + local source = ltn12.source.rewind(socket.source("until-closed", self.c)) + source(status) + return self.try(ltn12.pump.all(source, sink, step)) +end + +function metat.__index:close() + return self.c:close() +end + +----------------------------------------------------------------------------- +-- High level HTTP API +----------------------------------------------------------------------------- +local function adjusturi(reqt) + local u = reqt + -- if there is a proxy, we need the full url. otherwise, just a part. + if not reqt.proxy and not PROXY then + u = { + path = socket.try(reqt.path, "invalid path 'nil'"), + params = reqt.params, + query = reqt.query, + fragment = reqt.fragment + } + end + return url.build(u) +end + +local function adjustproxy(reqt) + local proxy = reqt.proxy or PROXY + if proxy then + proxy = url.parse(proxy) + return proxy.host, proxy.port or 3128 + else + return reqt.host, reqt.port + end +end + +local function adjustheaders(reqt) + -- default headers + local lower = { + ["user-agent"] = USERAGENT, + ["host"] = reqt.host, + ["connection"] = "close, TE", + ["te"] = "trailers" + } + -- if we have authentication information, pass it along + if reqt.user and reqt.password then + lower["authorization"] = + "Basic " .. (mime.b64(reqt.user .. ":" .. reqt.password)) + end + -- override with user headers + for i,v in base.pairs(reqt.headers or lower) do + lower[string.lower(i)] = v + end + return lower +end + +-- default url parts +local default = { + host = "", + port = PORT, + path ="/", + scheme = "http" +} + +local function adjustrequest(reqt) + -- parse url if provided + local nreqt = reqt.url and url.parse(reqt.url, default) or {} + -- explicit components override url + for i,v in base.pairs(reqt) do nreqt[i] = v end + if nreqt.port == "" then nreqt.port = 80 end + socket.try(nreqt.host and nreqt.host ~= "", + "invalid host '" .. base.tostring(nreqt.host) .. "'") + -- compute uri if user hasn't overriden + nreqt.uri = reqt.uri or adjusturi(nreqt) + -- ajust host and port if there is a proxy + nreqt.host, nreqt.port = adjustproxy(nreqt) + -- adjust headers in request + nreqt.headers = adjustheaders(nreqt) + return nreqt +end + +local function shouldredirect(reqt, code, headers) + return headers.location and + string.gsub(headers.location, "%s", "") ~= "" and + (reqt.redirect ~= false) and + (code == 301 or code == 302) and + (not reqt.method or reqt.method == "GET" or reqt.method == "HEAD") + and (not reqt.nredirects or reqt.nredirects < 5) +end + +local function shouldreceivebody(reqt, code) + if reqt.method == "HEAD" then return nil end + if code == 204 or code == 304 then return nil end + if code >= 100 and code < 200 then return nil end + return 1 +end + +-- forward declarations +local trequest, tredirect + +function tredirect(reqt, location) + local result, code, headers, status = trequest { + -- the RFC says the redirect URL has to be absolute, but some + -- servers do not respect that + url = url.absolute(reqt.url, location), + source = reqt.source, + sink = reqt.sink, + headers = reqt.headers, + proxy = reqt.proxy, + nredirects = (reqt.nredirects or 0) + 1, + create = reqt.create + } + -- pass location header back as a hint we redirected + headers = headers or {} + headers.location = headers.location or location + return result, code, headers, status +end + +function trequest(reqt) + -- we loop until we get what we want, or + -- until we are sure there is no way to get it + local nreqt = adjustrequest(reqt) + local h = open(nreqt.host, nreqt.port, nreqt.create) + -- send request line and headers + h:sendrequestline(nreqt.method, nreqt.uri) + h:sendheaders(nreqt.headers) + -- if there is a body, send it + if nreqt.source then + h:sendbody(nreqt.headers, nreqt.source, nreqt.step) + end + local code, status = h:receivestatusline() + -- if it is an HTTP/0.9 server, simply get the body and we are done + if not code then + h:receive09body(status, nreqt.sink, nreqt.step) + return 1, 200 + end + local headers + -- ignore any 100-continue messages + while code == 100 do + headers = h:receiveheaders() + code, status = h:receivestatusline() + end + headers = h:receiveheaders() + -- at this point we should have a honest reply from the server + -- we can't redirect if we already used the source, so we report the error + if shouldredirect(nreqt, code, headers) and not nreqt.source then + h:close() + return tredirect(reqt, headers.location) + end + -- here we are finally done + if shouldreceivebody(nreqt, code) then + h:receivebody(headers, nreqt.sink, nreqt.step) + end + h:close() + return 1, code, headers, status +end + +local function srequest(u, b) + local t = {} + local reqt = { + url = u, + sink = ltn12.sink.table(t) + } + if b then + reqt.source = ltn12.source.string(b) + reqt.headers = { + ["content-length"] = string.len(b), + ["content-type"] = "application/x-www-form-urlencoded" + } + reqt.method = "POST" + end + local code, headers, status = socket.skip(1, trequest(reqt)) + return table.concat(t), code, headers, status +end + +request = socket.protect(function(reqt, body) + if base.type(reqt) == "string" then return srequest(reqt, body) + else return trequest(reqt) end +end) diff --git a/Mi_Lua/socket/smtp.lua b/Mi_Lua/socket/smtp.lua new file mode 100644 index 0000000..8f3cfcf --- /dev/null +++ b/Mi_Lua/socket/smtp.lua @@ -0,0 +1,251 @@ +----------------------------------------------------------------------------- +-- SMTP client support for the Lua language. +-- LuaSocket toolkit. +-- Author: Diego Nehab +-- RCS ID: $Id: smtp.lua,v 1.46 2007/03/12 04:08:40 diego Exp $ +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Declare module and import dependencies +----------------------------------------------------------------------------- +local base = _G +local coroutine = require("coroutine") +local string = require("string") +local math = require("math") +local os = require("os") +local socket = require("socket") +local tp = require("socket.tp") +local ltn12 = require("ltn12") +local mime = require("mime") +module("socket.smtp") + +----------------------------------------------------------------------------- +-- Program constants +----------------------------------------------------------------------------- +-- timeout for connection +TIMEOUT = 60 +-- default server used to send e-mails +SERVER = "localhost" +-- default port +PORT = 25 +-- domain used in HELO command and default sendmail +-- If we are under a CGI, try to get from environment +DOMAIN = os.getenv("SERVER_NAME") or "localhost" +-- default time zone (means we don't know) +ZONE = "-0000" + +--------------------------------------------------------------------------- +-- Low level SMTP API +----------------------------------------------------------------------------- +local metat = { __index = {} } + +function metat.__index:greet(domain) + self.try(self.tp:check("2..")) + self.try(self.tp:command("EHLO", domain or DOMAIN)) + return socket.skip(1, self.try(self.tp:check("2.."))) +end + +function metat.__index:mail(from) + self.try(self.tp:command("MAIL", "FROM:" .. from)) + return self.try(self.tp:check("2..")) +end + +function metat.__index:rcpt(to) + self.try(self.tp:command("RCPT", "TO:" .. to)) + return self.try(self.tp:check("2..")) +end + +function metat.__index:data(src, step) + self.try(self.tp:command("DATA")) + self.try(self.tp:check("3..")) + self.try(self.tp:source(src, step)) + self.try(self.tp:send("\r\n.\r\n")) + return self.try(self.tp:check("2..")) +end + +function metat.__index:quit() + self.try(self.tp:command("QUIT")) + return self.try(self.tp:check("2..")) +end + +function metat.__index:close() + return self.tp:close() +end + +function metat.__index:login(user, password) + self.try(self.tp:command("AUTH", "LOGIN")) + self.try(self.tp:check("3..")) + self.try(self.tp:command(mime.b64(user))) + self.try(self.tp:check("3..")) + self.try(self.tp:command(mime.b64(password))) + return self.try(self.tp:check("2..")) +end + +function metat.__index:plain(user, password) + local auth = "PLAIN " .. mime.b64("\0" .. user .. "\0" .. password) + self.try(self.tp:command("AUTH", auth)) + return self.try(self.tp:check("2..")) +end + +function metat.__index:auth(user, password, ext) + if not user or not password then return 1 end + if string.find(ext, "AUTH[^\n]+LOGIN") then + return self:login(user, password) + elseif string.find(ext, "AUTH[^\n]+PLAIN") then + return self:plain(user, password) + else + self.try(nil, "authentication not supported") + end +end + +-- send message or throw an exception +function metat.__index:send(mailt) + self:mail(mailt.from) + if base.type(mailt.rcpt) == "table" then + for i,v in base.ipairs(mailt.rcpt) do + self:rcpt(v) + end + else + self:rcpt(mailt.rcpt) + end + self:data(ltn12.source.chain(mailt.source, mime.stuff()), mailt.step) +end + +function open(server, port, create) + local tp = socket.try(tp.connect(server or SERVER, port or PORT, + TIMEOUT, create)) + local s = base.setmetatable({tp = tp}, metat) + -- make sure tp is closed if we get an exception + s.try = socket.newtry(function() + s:close() + end) + return s +end + +-- convert headers to lowercase +local function lower_headers(headers) + local lower = {} + for i,v in base.pairs(headers or lower) do + lower[string.lower(i)] = v + end + return lower +end + +--------------------------------------------------------------------------- +-- Multipart message source +----------------------------------------------------------------------------- +-- returns a hopefully unique mime boundary +local seqno = 0 +local function newboundary() + seqno = seqno + 1 + return string.format('%s%05d==%05u', os.date('%d%m%Y%H%M%S'), + math.random(0, 99999), seqno) +end + +-- send_message forward declaration +local send_message + +-- yield the headers all at once, it's faster +local function send_headers(headers) + local h = "\r\n" + for i,v in base.pairs(headers) do + h = i .. ': ' .. v .. "\r\n" .. h + end + coroutine.yield(h) +end + +-- yield multipart message body from a multipart message table +local function send_multipart(mesgt) + -- make sure we have our boundary and send headers + local bd = newboundary() + local headers = lower_headers(mesgt.headers or {}) + headers['content-type'] = headers['content-type'] or 'multipart/mixed' + headers['content-type'] = headers['content-type'] .. + '; boundary="' .. bd .. '"' + send_headers(headers) + -- send preamble + if mesgt.body.preamble then + coroutine.yield(mesgt.body.preamble) + coroutine.yield("\r\n") + end + -- send each part separated by a boundary + for i, m in base.ipairs(mesgt.body) do + coroutine.yield("\r\n--" .. bd .. "\r\n") + send_message(m) + end + -- send last boundary + coroutine.yield("\r\n--" .. bd .. "--\r\n\r\n") + -- send epilogue + if mesgt.body.epilogue then + coroutine.yield(mesgt.body.epilogue) + coroutine.yield("\r\n") + end +end + +-- yield message body from a source +local function send_source(mesgt) + -- make sure we have a content-type + local headers = lower_headers(mesgt.headers or {}) + headers['content-type'] = headers['content-type'] or + 'text/plain; charset="iso-8859-1"' + send_headers(headers) + -- send body from source + while true do + local chunk, err = mesgt.body() + if err then coroutine.yield(nil, err) + elseif chunk then coroutine.yield(chunk) + else break end + end +end + +-- yield message body from a string +local function send_string(mesgt) + -- make sure we have a content-type + local headers = lower_headers(mesgt.headers or {}) + headers['content-type'] = headers['content-type'] or + 'text/plain; charset="iso-8859-1"' + send_headers(headers) + -- send body from string + coroutine.yield(mesgt.body) +end + +-- message source +function send_message(mesgt) + if base.type(mesgt.body) == "table" then send_multipart(mesgt) + elseif base.type(mesgt.body) == "function" then send_source(mesgt) + else send_string(mesgt) end +end + +-- set defaul headers +local function adjust_headers(mesgt) + local lower = lower_headers(mesgt.headers) + lower["date"] = lower["date"] or + os.date("!%a, %d %b %Y %H:%M:%S ") .. (mesgt.zone or ZONE) + lower["x-mailer"] = lower["x-mailer"] or socket._VERSION + -- this can't be overriden + lower["mime-version"] = "1.0" + return lower +end + +function message(mesgt) + mesgt.headers = adjust_headers(mesgt) + -- create and return message source + local co = coroutine.create(function() send_message(mesgt) end) + return function() + local ret, a, b = coroutine.resume(co) + if ret then return a, b + else return nil, a end + end +end + +--------------------------------------------------------------------------- +-- High level SMTP API +----------------------------------------------------------------------------- +send = socket.protect(function(mailt) + local s = open(mailt.server, mailt.port, mailt.create) + local ext = s:greet(mailt.domain) + s:auth(mailt.user, mailt.password, ext) + s:send(mailt) + s:quit() + return s:close() +end) diff --git a/Mi_Lua/socket/tp.lua b/Mi_Lua/socket/tp.lua new file mode 100644 index 0000000..0683869 --- /dev/null +++ b/Mi_Lua/socket/tp.lua @@ -0,0 +1,123 @@ +----------------------------------------------------------------------------- +-- Unified SMTP/FTP subsystem +-- LuaSocket toolkit. +-- Author: Diego Nehab +-- RCS ID: $Id: tp.lua,v 1.22 2006/03/14 09:04:15 diego Exp $ +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Declare module and import dependencies +----------------------------------------------------------------------------- +local base = _G +local string = require("string") +local socket = require("socket") +local ltn12 = require("ltn12") +module("socket.tp") + +----------------------------------------------------------------------------- +-- Program constants +----------------------------------------------------------------------------- +TIMEOUT = 60 + +----------------------------------------------------------------------------- +-- Implementation +----------------------------------------------------------------------------- +-- gets server reply (works for SMTP and FTP) +local function get_reply(c) + local code, current, sep + local line, err = c:receive() + local reply = line + if err then return nil, err end + code, sep = socket.skip(2, string.find(line, "^(%d%d%d)(.?)")) + if not code then return nil, "invalid server reply" end + if sep == "-" then -- reply is multiline + repeat + line, err = c:receive() + if err then return nil, err end + current, sep = socket.skip(2, string.find(line, "^(%d%d%d)(.?)")) + reply = reply .. "\n" .. line + -- reply ends with same code + until code == current and sep == " " + end + return code, reply +end + +-- metatable for sock object +local metat = { __index = {} } + +function metat.__index:check(ok) + local code, reply = get_reply(self.c) + if not code then return nil, reply end + if base.type(ok) ~= "function" then + if base.type(ok) == "table" then + for i, v in base.ipairs(ok) do + if string.find(code, v) then + return base.tonumber(code), reply + end + end + return nil, reply + else + if string.find(code, ok) then return base.tonumber(code), reply + else return nil, reply end + end + else return ok(base.tonumber(code), reply) end +end + +function metat.__index:command(cmd, arg) + if arg then + return self.c:send(cmd .. " " .. arg.. "\r\n") + else + return self.c:send(cmd .. "\r\n") + end +end + +function metat.__index:sink(snk, pat) + local chunk, err = c:receive(pat) + return snk(chunk, err) +end + +function metat.__index:send(data) + return self.c:send(data) +end + +function metat.__index:receive(pat) + return self.c:receive(pat) +end + +function metat.__index:getfd() + return self.c:getfd() +end + +function metat.__index:dirty() + return self.c:dirty() +end + +function metat.__index:getcontrol() + return self.c +end + +function metat.__index:source(source, step) + local sink = socket.sink("keep-open", self.c) + local ret, err = ltn12.pump.all(source, sink, step or ltn12.pump.step) + return ret, err +end + +-- closes the underlying c +function metat.__index:close() + self.c:close() + return 1 +end + +-- connect with server and return c object +function connect(host, port, timeout, create) + local c, e = (create or socket.tcp)() + if not c then return nil, e end + c:settimeout(timeout or TIMEOUT) + local r, e = c:connect(host, port) + if not r then + c:close() + return nil, e + end + return base.setmetatable({c = c}, metat) +end + diff --git a/Mi_Lua/socket/url.lua b/Mi_Lua/socket/url.lua new file mode 100644 index 0000000..0e31d8a --- /dev/null +++ b/Mi_Lua/socket/url.lua @@ -0,0 +1,297 @@ +----------------------------------------------------------------------------- +-- URI parsing, composition and relative URL resolution +-- LuaSocket toolkit. +-- Author: Diego Nehab +-- RCS ID: $Id: url.lua,v 1.38 2006/04/03 04:45:42 diego Exp $ +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Declare module +----------------------------------------------------------------------------- +local string = require("string") +local base = _G +local table = require("table") +module("socket.url") + +----------------------------------------------------------------------------- +-- Module version +----------------------------------------------------------------------------- +_VERSION = "URL 1.0.1" + +----------------------------------------------------------------------------- +-- Encodes a string into its escaped hexadecimal representation +-- Input +-- s: binary string to be encoded +-- Returns +-- escaped representation of string binary +----------------------------------------------------------------------------- +function escape(s) + return string.gsub(s, "([^A-Za-z0-9_])", function(c) + return string.format("%%%02x", string.byte(c)) + end) +end + +----------------------------------------------------------------------------- +-- Protects a path segment, to prevent it from interfering with the +-- url parsing. +-- Input +-- s: binary string to be encoded +-- Returns +-- escaped representation of string binary +----------------------------------------------------------------------------- +local function make_set(t) + local s = {} + for i,v in base.ipairs(t) do + s[t[i]] = 1 + end + return s +end + +-- these are allowed withing a path segment, along with alphanum +-- other characters must be escaped +local segment_set = make_set { + "-", "_", ".", "!", "~", "*", "'", "(", + ")", ":", "@", "&", "=", "+", "$", ",", +} + +local function protect_segment(s) + return string.gsub(s, "([^A-Za-z0-9_])", function (c) + if segment_set[c] then return c + else return string.format("%%%02x", string.byte(c)) end + end) +end + +----------------------------------------------------------------------------- +-- Encodes a string into its escaped hexadecimal representation +-- Input +-- s: binary string to be encoded +-- Returns +-- escaped representation of string binary +----------------------------------------------------------------------------- +function unescape(s) + return string.gsub(s, "%%(%x%x)", function(hex) + return string.char(base.tonumber(hex, 16)) + end) +end + +----------------------------------------------------------------------------- +-- Builds a path from a base path and a relative path +-- Input +-- base_path +-- relative_path +-- Returns +-- corresponding absolute path +----------------------------------------------------------------------------- +local function absolute_path(base_path, relative_path) + if string.sub(relative_path, 1, 1) == "/" then return relative_path end + local path = string.gsub(base_path, "[^/]*$", "") + path = path .. relative_path + path = string.gsub(path, "([^/]*%./)", function (s) + if s ~= "./" then return s else return "" end + end) + path = string.gsub(path, "/%.$", "/") + local reduced + while reduced ~= path do + reduced = path + path = string.gsub(reduced, "([^/]*/%.%./)", function (s) + if s ~= "../../" then return "" else return s end + end) + end + path = string.gsub(reduced, "([^/]*/%.%.)$", function (s) + if s ~= "../.." then return "" else return s end + end) + return path +end + +----------------------------------------------------------------------------- +-- Parses a url and returns a table with all its parts according to RFC 2396 +-- The following grammar describes the names given to the URL parts +-- ::= :///;?# +-- ::= @: +-- ::= [:] +-- :: = {/} +-- Input +-- url: uniform resource locator of request +-- default: table with default values for each field +-- Returns +-- table with the following fields, where RFC naming conventions have +-- been preserved: +-- scheme, authority, userinfo, user, password, host, port, +-- path, params, query, fragment +-- Obs: +-- the leading '/' in {/} is considered part of +----------------------------------------------------------------------------- +function parse(url, default) + -- initialize default parameters + local parsed = {} + for i,v in base.pairs(default or parsed) do parsed[i] = v end + -- empty url is parsed to nil + if not url or url == "" then return nil, "invalid url" end + -- remove whitespace + -- url = string.gsub(url, "%s", "") + -- get fragment + url = string.gsub(url, "#(.*)$", function(f) + parsed.fragment = f + return "" + end) + -- get scheme + url = string.gsub(url, "^([%w][%w%+%-%.]*)%:", + function(s) parsed.scheme = s; return "" end) + -- get authority + url = string.gsub(url, "^//([^/]*)", function(n) + parsed.authority = n + return "" + end) + -- get query stringing + url = string.gsub(url, "%?(.*)", function(q) + parsed.query = q + return "" + end) + -- get params + url = string.gsub(url, "%;(.*)", function(p) + parsed.params = p + return "" + end) + -- path is whatever was left + if url ~= "" then parsed.path = url end + local authority = parsed.authority + if not authority then return parsed end + authority = string.gsub(authority,"^([^@]*)@", + function(u) parsed.userinfo = u; return "" end) + authority = string.gsub(authority, ":([^:]*)$", + function(p) parsed.port = p; return "" end) + if authority ~= "" then parsed.host = authority end + local userinfo = parsed.userinfo + if not userinfo then return parsed end + userinfo = string.gsub(userinfo, ":([^:]*)$", + function(p) parsed.password = p; return "" end) + parsed.user = userinfo + return parsed +end + +----------------------------------------------------------------------------- +-- Rebuilds a parsed URL from its components. +-- Components are protected if any reserved or unallowed characters are found +-- Input +-- parsed: parsed URL, as returned by parse +-- Returns +-- a stringing with the corresponding URL +----------------------------------------------------------------------------- +function build(parsed) + local ppath = parse_path(parsed.path or "") + local url = build_path(ppath) + if parsed.params then url = url .. ";" .. parsed.params end + if parsed.query then url = url .. "?" .. parsed.query end + local authority = parsed.authority + if parsed.host then + authority = parsed.host + if parsed.port then authority = authority .. ":" .. parsed.port end + local userinfo = parsed.userinfo + if parsed.user then + userinfo = parsed.user + if parsed.password then + userinfo = userinfo .. ":" .. parsed.password + end + end + if userinfo then authority = userinfo .. "@" .. authority end + end + if authority then url = "//" .. authority .. url end + if parsed.scheme then url = parsed.scheme .. ":" .. url end + if parsed.fragment then url = url .. "#" .. parsed.fragment end + -- url = string.gsub(url, "%s", "") + return url +end + +----------------------------------------------------------------------------- +-- Builds a absolute URL from a base and a relative URL according to RFC 2396 +-- Input +-- base_url +-- relative_url +-- Returns +-- corresponding absolute url +----------------------------------------------------------------------------- +function absolute(base_url, relative_url) + if base.type(base_url) == "table" then + base_parsed = base_url + base_url = build(base_parsed) + else + base_parsed = parse(base_url) + end + local relative_parsed = parse(relative_url) + if not base_parsed then return relative_url + elseif not relative_parsed then return base_url + elseif relative_parsed.scheme then return relative_url + else + relative_parsed.scheme = base_parsed.scheme + if not relative_parsed.authority then + relative_parsed.authority = base_parsed.authority + if not relative_parsed.path then + relative_parsed.path = base_parsed.path + if not relative_parsed.params then + relative_parsed.params = base_parsed.params + if not relative_parsed.query then + relative_parsed.query = base_parsed.query + end + end + else + relative_parsed.path = absolute_path(base_parsed.path or "", + relative_parsed.path) + end + end + return build(relative_parsed) + end +end + +----------------------------------------------------------------------------- +-- Breaks a path into its segments, unescaping the segments +-- Input +-- path +-- Returns +-- segment: a table with one entry per segment +----------------------------------------------------------------------------- +function parse_path(path) + local parsed = {} + path = path or "" + --path = string.gsub(path, "%s", "") + string.gsub(path, "([^/]+)", function (s) table.insert(parsed, s) end) + for i = 1, table.getn(parsed) do + parsed[i] = unescape(parsed[i]) + end + if string.sub(path, 1, 1) == "/" then parsed.is_absolute = 1 end + if string.sub(path, -1, -1) == "/" then parsed.is_directory = 1 end + return parsed +end + +----------------------------------------------------------------------------- +-- Builds a path component from its segments, escaping protected characters. +-- Input +-- parsed: path segments +-- unsafe: if true, segments are not protected before path is built +-- Returns +-- path: corresponding path stringing +----------------------------------------------------------------------------- +function build_path(parsed, unsafe) + local path = "" + local n = table.getn(parsed) + if unsafe then + for i = 1, n-1 do + path = path .. parsed[i] + path = path .. "/" + end + if n > 0 then + path = path .. parsed[n] + if parsed.is_directory then path = path .. "/" end + end + else + for i = 1, n-1 do + path = path .. protect_segment(parsed[i]) + path = path .. "/" + end + if n > 0 then + path = path .. protect_segment(parsed[n]) + if parsed.is_directory then path = path .. "/" end + end + end + if parsed.is_absolute then path = "/" .. path end + return path +end diff --git a/Mi_Lua/ssl.lua b/Mi_Lua/ssl.lua new file mode 100644 index 0000000..18a07b3 --- /dev/null +++ b/Mi_Lua/ssl.lua @@ -0,0 +1,93 @@ +------------------------------------------------------------------------------ +-- LuaSec 0.4 +-- Copyright (C) 2006-2009 Bruno Silvestre +-- +------------------------------------------------------------------------------ + +module("ssl", package.seeall) + +require("ssl.core") +require("ssl.context") + + +_VERSION = "0.4" +_COPYRIGHT = "LuaSec 0.4 - Copyright (C) 2006-2009 Bruno Silvestre\n" .. + "LuaSocket 2.0.2 - Copyright (C) 2004-2007 Diego Nehab" + +-- Export functions +rawconnection = core.rawconnection +rawcontext = context.rawcontext + +-- +-- +-- +local function optexec(func, param, ctx) + if param then + if type(param) == "table" then + return func(ctx, unpack(param)) + else + return func(ctx, param) + end + end + return true +end + +-- +-- +-- +function newcontext(cfg) + local succ, msg, ctx + -- Create the context + ctx, msg = context.create(cfg.protocol) + if not ctx then return nil, msg end + -- Mode + succ, msg = context.setmode(ctx, cfg.mode) + if not succ then return nil, msg end + -- Load the key + if cfg.key then + succ, msg = context.loadkey(ctx, cfg.key, cfg.password) + if not succ then return nil, msg end + end + -- Load the certificate + if cfg.certificate then + succ, msg = context.loadcert(ctx, cfg.certificate) + if not succ then return nil, msg end + end + -- Load the CA certificates + if cfg.cafile or cfg.capath then + succ, msg = context.locations(ctx, cfg.cafile, cfg.capath) + if not succ then return nil, msg end + end + -- Set the verification options + succ, msg = optexec(context.setverify, cfg.verify, ctx) + if not succ then return nil, msg end + -- Set SSL options + succ, msg = optexec(context.setoptions, cfg.options, ctx) + if not succ then return nil, msg end + -- Set the depth for certificate verification + if cfg.depth then + succ, msg = context.setdepth(ctx, cfg.depth) + if not succ then return nil, msg end + end + return ctx +end + +-- +-- +-- +function wrap(sock, cfg) + local ctx, msg + if type(cfg) == "table" then + ctx, msg = newcontext(cfg) + if not ctx then return nil, msg end + else + ctx = cfg + end + local s, msg = core.create(ctx) + if s then + core.setfd(s, sock:getfd()) + sock:setfd(core.invalidfd) + return s + end + return nil, msg +end diff --git a/Mi_Lua/ssl.so b/Mi_Lua/ssl.so new file mode 100644 index 0000000000000000000000000000000000000000..ffc0cc6c64bdb9f8f51085c088fb676636433380 GIT binary patch literal 23144 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 z zhv&agFwb-tsSGKP4KP>;6NOka#lIngBKfT#Yyw2&P5CWHVIot5JYM+Zee$_DJS6`3 zG=2jIzo21Jo(_n0R$xD$Y6ATCKK!1e_iV(=%l}XR@94m@ODPPTz@4tuLX1rjxkG@s zAuV1bgae$Cc~g8Kp)f0MRXzl0f@B#WeYhErPp1pRdjRP_gEDW3lWw_j%7wmB7Y06b zp{Tk|HWxD`42rpcITWo@<#9sz<#s}JxLcOz#N&h}e(NB_Io}akULlSVhLO}$5BaxW zgOd`#59fKMkRSOa4Y&&qcyJNs2%rhL5wK<<<~iU1_EKp;-gVm!I0K8rZonm2H1-2N z55A8B9-BA8*Ia}7_$i!60zQu)!$N>_uSFZc{_D^mAU_7z1bAW@^apqlum$j4wDTUy zm;VHGiSl~DAm9h!=RG&BSh3Wszwy@FuQnGnENGZ-&JTqaG=@Tp%=(py)?}<3`z7oW zuyFMxt`;seSM;7NK2|gD)*H>nhQ?1egswIhHY|~?8yAEYhZdOidb8zo(OoPNYfTE{ za-e0On73S%S58`9VNCW-3)C1F8JGBH7`493jk$(skl#kamqbKEN35qqG{m=c_iXEO zVKyTgAP&o1B#o~eHOLPmHOSB3G|2B=HDprqyCDsU4)>c>E=@F+$>3X3lE%aDZUalO zi(xw=8n6XGE{4wsHQ7^l65bBRwK65gpK9pf?+*D4Pil1hRxp$9 z|6UV(u3?-Xzst#VzJ9M)beLM=L(njFYI-3*%8%(FA;;H&xxnQ0g`#o%4&YOfsO!!* zkUoO04fPi)I(}y+{SkC2(6E2<KzJ;f%S{Jt^hPO3bBl#llKB9h0C>vCi* z{EG$Z;h322Me^tvrvuH1aV2zYvKxtf+3(QXNVmJ+3`ABwo{sh+$L1loSklSOkjk@; j=_^R!l~%>#iuFQh;Z4%8j 0 then + hostname = t.name + else + hostname = dhcpname + end + end + end + return hostname == '' and mac or hostname +end + +function get_wan_dev_name() + local ubus = require ("ubus") + local conn = ubus.connect() + if not conn then + elog("Failed to connect to ubusd") + end + local status = conn:call("network.interface.wan", "status",{}) + conn:close() + return (status.l3_device and status.l3_device) or status.device +end + +function get_lan_dev_name() + local ubus = require ("ubus") + local conn = ubus.connect() + if not conn then + elog("Failed to connect to ubusd") + end + local status = conn:call("network.interface.lan", "status",{}) + conn:close() + return (status.l3_device and status.l3_device) or status.device +end + +function trafficd_lua_done() + os.execute("killall -q -s 10 noflushd"); +end diff --git a/Mi_Lua/ubus.so b/Mi_Lua/ubus.so new file mode 100644 index 0000000000000000000000000000000000000000..3458b5a1cc89511737854b9b4dc86a247567f856 GIT binary patch literal 12968 zcmd^GeQ;dWb-!# zS(w5D-X&sz!2odakDECO5%CnDolj%%|5GUDIu-_y#&D6t^pvtfAV}1H6I5oR-Cz-#7&?p z0ha+7hySgIvE@@@qYwP$nEY3dR}+{Ix&}}Vr~Tmj%S&qm^7z@GvvP-Pu` z0tG*y8t`X;Qh@#}L*a9P&#KB=g|Afja)o^guLZsq@EKJuMC<4b9=DyTB8uF!%k#zS zOj`ADY>#PW_Pw2*R1eEvDky)GlS)10Ut7@r@dEzu(SGt9b_1_?Yvu{3F)-Vo)#T~V zds>|0tq z*}l#Z1~vh|gFb!yZY+?$t)RTW!2aC@{6A=A`u|`-nd_hPm>>cL`p%%N$A7e-KNFfh z_GeoG|NVmgyjmddFDPH5wWmH7x2uU+A?AwH1^!$D0X<#|3)(jq^k++fy(0`O1p!~H0{om5svw!z$WfSyv$aiA_FN2;6tg7=h`_x}mpufAI zy{Yl3zgKI|vIuSIjrEGq4ejeg-I4yt*4}|+q`!Ur>S#O`X%BZsBR22DIiaqhFcpNO zz4t;;(-7LyAMT5UdShGSsCQa_p!UYQLmQR7)zR?4Kx9Bf2g9Mn;6P8TH!A5^WGE?# zWIQ^M?C*_jwM*L)5mnt8k4Gcnm@4(gl98>EegWBNBqlne@y@=1t)XytH@u8QyQSDb zB$NA(CuznC+ScmCWYHVT|JSmy9T?%$uJwMvgS-%DBKT|{qcT{pQMAd z9_k$2f*|zTyF zZ3yTVr!TgQR5u%DsWQ|Pj&(;74awWmABoshG#iBs zCgteK+|x5k=Hg&qCxUpPdkzWZvZtXg44qvW>5JcOXC_-ld$;x^XFa8Y-htQ!ZOQnB zf=og>Nj+_h%?EnS0k>zSi1v0;&NmRZ$@q{WyMTRu5sJu&hC-bK$ggC$KZ*G_ONTwb|+8yfd?QeiG^tnFV-&a2b zc3*FOv^O?5R6p1Sr@FSr1|#9l-UfPCzs%p*T<`x(y}zje9cu9TR{S5DSb=4X?*}|T z_b8lDIH~ZE!uKhhQh2+T%+FAx;qEz&TEgmy{FF;f#wtVVoET>X(U&uqPAa@clz# zY~mBdIE4R{7zh6|#Pfvs1#y`WY2u4;o+8FUdy*LE);Eda&onU(-ft6Qqd8BE7lwC< zc^HR33-E&EA;zJ*gct|xGGe@Rc!_JUKM~`=UQLXbp@qbF=`e|LuwG1T;$4Qg7UxG| z#KBKohxZ#|?0+r9IN+}&#*0sY7%u^9h;bfRM|>6bXX2$o1c`BAZzuK(aR)J8?CvC9 zCPW7@UY@#%@nY6Pj2E~lF%l|4j2D6=F<#P!h;i7xj~FjsDPp_?Y$px~v5UA>h&{xs zgcv1WEyMxhH9{OBzFvqYiPs5X5o0``CcaUKqr^Ai{btq9G}^DxOElWl=xUAjYIKQ43ynViM=kyuJ*m-YjXtB%6B>O=qsKM+ zxJHj@^ihqrH2RQ6k81QTjZSIwkVYp!H=Bp^%>v**oOhY~(n9P@yUho(F0&);_U}s< zi;cO$7E}d$aSby;%{kGDgtKxDr<864>zwl)dTP zZ{D9dhmpwL54}R%o}1HQ8MNu}1^P;P(P3p+C(!2OC>tV>EkQf+*1plLLQKs8d;)-N zXzEhH5ki6sUQAnvOk7@9?1GX z_MO+t1Y{xSw4wZ5$YYC?y6G$RpJSg=R;E$fGor`ZpV!pP_Cp>R?~ixzP6es4Mm&PVj}c7QhGioS{F=8`{8@ zJmuJW4DyCQkQHO_OKi%#IWtq^@cs_Gd1!YA<4m1wPo0LQbDn9B%LAwr9i!P2%5_+k zlq+VPxD7VNtj;6QiJgpfvpzhDx_e>6Kz-H3-t6?glatSz$aBQ)Jo1;h!#RE$axTU) zX!nnP#;MOgk)O>EJ7;T#(4MmAk*93?o%d&^ir_!T!jssM{Sn)No&z0yps5QvAp3ZW z^gEct(MbAzm*P|3FG0KD=XuP#DseE+xOmKi`M;z-Ro{WLjt@{>tNO6o?nBv^?X{|8 z_^_0plh~K0PWnavOH%vN^y9CTz4Zz+C+ejBU=GI;Io7PZUGjsuPboi{-y2a^jGVx6 zsAdiPsb@RbSVJ3#TMXqA(;lAH?SqAxfKPkjUR+(+JNfDavL>wQY@CDmf@r0^fi zKjL<$=a^he_U5VIgF41m1Z~~O-BQN;yGsAJ6y~^`Mf?wy7qpl6Ih)Krd=NKK7`*4}^0jg&6^l4@LD+<$xd8)517&ilbCZ5FN zw(cW*_zBuF{v1E7Lo)aNaKVS19RquvI7eB;LHA)q*&b4uK1_jTymcSC;X?qMoXkOt zkt={0d-vy`Q|%bj6`+X?GbP87dm;2sF|W8@aoutuCd}OwZJ>M^cup>T9d*_0ONZT$ zKS!DV;+3eB>(TuzBj2kq@9;WRIv27_MJnqxIsHQMVG~zR6_an?WjXAhWjekNL{{hG`?nSUi9d2oVNA^C}gXWxA zsPsB>;(hYuoRxWAQ=|Bd#U9YKn}A)GUws$;Vhv?3aU7$vt<9grd#1`oIrb&s$vp^t z`xV+b>w;5f1h!`>^Y6%U*a&}IsCx-QyUg0+NkY37)S2f4Q*-$rELO3V>?j;Tg0O3bHZ-IEoE z^FwIo^!-VtljW$QSHc#{N0i)S3Uhy<49790#mbWWVD26*R$oOOW7V(K-LBg9&9=32 zZ_Bp!IDA{t%(LIl(&^maYBD=ObI)`u9rWoQ)ZL-ZDBM@?Lz%Y!L!FOU*Md5c2xi|x zU6tP+3&!%%;?ndN!1I9S8qI!}j_yiFr~`EvH(A$)vRjk+BFbg{r}Cv@*XwNK@jsNm zR-Hxg8KN|GjAu>EbI9?mA}o}lYa82H56SojK(j6VSOA)Ftwme<%laDB$?+bueYj4_ zU+6n|)c+ebf7tGGD7#hP=ZG)l-kzD+y|ZyQ-jOckAkW10kQ4Bq^*fE-vJXaLXVx<| zlCJ}g=SXkjp}f)YpjgLUM1zAo9lL!cr^dYA1|~k z1J6V|o&%ioqZjK2;UYj1n<38^_`;IsIex}7jp8QhDD zt)F2oZI(G^SoWG(?A@8Q$LMkF-#fD@;g#wb5-`s z-kAl(-r)+j_k~KsJK}Zw!+hsW2UouKy5V1)Eymf`jrXA|khd7~sX72YpG{#8nVJXS znR^Ct;yNL`r)3OH)Q$O1XIST6Y-Q@I9>~DXYj`ikJI#K%rZisuo!2f$zwqg9O8EC< zZ1(4yP{&xlV~BIiXTy*4K6E^caiJ~JzXn~RXo2}*C>pZ4BZ#3>eiU+s9h(}qV_vW= z&PP*5;GyhX==Y4;`k7lt!DGC4a~x6~X_45OehmFGF#Z#Gci`S-1g%UWfHk@UYY_Lx zz`k@3_D7sH~C}%eK1i&!azOh(B(m z$nO>&|NgVsGc!%s!hZ8hu)jJ>fAFp{#j~yp{d^bwWgfdzR_1Nh=V`6a)xoFo^R<4i zG#}63k22o}oLCeqKI2@g(U)oMt_IEcunj)T)-XnIzQcIlnnT{y$a=`|ot*vrsQ6*d zp5wO(uFaTlIr|&Qf&41X*3YHhU=C+gMAROyQt1UagwN*PL+)zahpCl)J_c zmcRVUboqAy+pumweWR86`sQR>1S8*s$ybX4+tV)7OuPNHY25fizSPZ@mm~lEsgt{0 zMtROyfVh9p%GEB|lfk^lUXaKNZzk(9Ds%4TSZ9~3db2+({JAWBxE*pvU`^Ic527r- zRq41&ONyK#yu1Amb?7gc*gXPxaMImGR;j{ zOv7i&%tx@-i&v}0AIO%Oo!Rm-d>5wb_d$io|<6` zc8|dBQBNS9xjfLGy@nuge+>YF+5N%LuNmz+NbPbV;RWRq!3TTi};rh*HO+`djK|umw|5KnA~5rBlocd!&o!%EfIX$gWhuJ zWv&@m%gZqb%8lzrGw$0SF<{4VT{27?lL83>n{Ix8X5Jb%VIM*aM9@C3JO%$hO2)Op zi#+I9h4#5eA(t}8GB4trc9?T;zLlA2uBe#tG*!;8tgV2492J)3Saxj9Uyt(;bE^D> z+z8qm=BWG*PCsa$`KZ^qHk2bTk$)=R9bapIFqXMEMDo*4c#c#R`DYsE;0 z^R5Z)(O>oj81LP{M)4@-x2ORh^DoUf&hjq89MFF|lfRSmcXSbpoAOG88N;O%{OV0k zsH;<5z1S8XG-HuSx0#Ha30y=m!=`;}U{(-KdSl&@A$d(gv~Jwk`gzgbx~grh*wnu9 zhMTV!H{9I5yqSNSmho@na{g@*t8TfaZEfq#!nxXD-#!=+yxCyiR~YEs8Ve@}`*Hb2 zUL)v=#s_eNfVXgBg7+F=dTSR}Iy~LT)HC?D!{@TA@SPqr_LIQXz&_v}91xcQAHYZR z7GivSS`FNeiLegX3w$$h8XqTa1zrfeS=HNG?fX^7FbND)oKW~5fwAU@=Ye^(u^1B` z<0tRQVJ^%2STH4jgNJE(_ZBY6-{RqtETf5d9)LM7j$<)Fu8H#)NQ}Gq9+>lh@wrC8 zgc!qb%wUN$oWQ47dH?eg;2s!5Qi~LfGYpta@(wEw6XKnjnK)(syASURJI@{H)c+@N z`dx@${s!(euU@^vTyp)*w_ag3`I>yo%w>LmQ={MiX>-Y%NGC3Yn2CN|6T_XWfyfm~ zx~o4vFp$JWtooa;HyeG8SNr@|n9aTx*}AdGzuezsF43E{E`{kZZ;J?H5y+y?)VGS_ z(mAaq##~p0r^>k4xWrv;)VLNIxKHXE=)r9x%t+zu2@muLU-!1yz_vac1uZwjSK55x%Z?Umx#o`C2!w_wf=D?&c&hlm7<~^PY2p`Of)4Sc3w~d^m3i%zMhR zp7Vw<4hH87fij#woQEt^j`NJb`Q<#E*L-RLSbwD)=M#bRiS78X3Fp<707s7VkHC3J zIjEkMYXVK(oY%ZROQ1i_GBc(k`((R8WfDjOVIsFJ=B<9sLAM8>-NSpZ}|Os)%ju9=kM z-K3t6kc)#)`;?19F8UF2NllJ-oDz_`19i^h_%MtLjs^8`tvdxdjxWo6D97>L4RGvo z-5tZ!tYU?FC`Z@_aOAj7yj=g`{XGad>XmjOcM5VQ%Fg5X_XH}a&#xGii#S4xNB-9U e(3Nml^++Iy&OVgkL+td8<;{@$8xrbqwS`#kr% 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 from and tmac <= to then + maciden = {} + local dtype = {["c"] = 0, ["p"] = 0, ["n"] = ""} + local priority = 2 + if rule["type"] then + dtype = rule["type"] + end + if rule.priority then + priority = tonumber(rule.priority) + end + maciden["name"] = rule.company + maciden["icon"] = rule.icon + maciden["type"] = dtype + maciden["priority"] = priority + end + end + end + else + local NixioFs = require("nixio.fs") + if not NixioFs.access(XQConfigs.OUI_FILEPATH) then + LuciUtil.exec("unzip -d /tmp".." "..XQConfigs.OUI_ZIP_FILEPATH) + end + if NixioFs.access(XQConfigs.OUI_FILEPATH) then + key = string.upper(string.sub(string.gsub(mac,":","-"),1,8)) + local line = LuciUtil.trim(LuciUtil.exec("sed -n '/"..key.."/p' "..XQConfigs.OUI_FILEPATH)) + if not XQFunction.isStrNil(line) then + local company = LuciUtil.trim(LuciUtil.split(line,key)[2]) + local icon = company:match("ICON:(%S+)") + maciden = {} + if icon then + maciden["name"] = company:match("(.+)ICON:%S+") or "" + maciden["icon"] = icon + else + maciden["name"] = company + maciden["icon"] = "" + end + maciden["type"] = {["c"] = 0, ["p"] = 0, ["n"] = ""} + maciden["priority"] = 2 + end + end + end + end + if not XQFunction.isStrNil(dhcpname) then + dhcpname = string.lower(dhcpname) + for _, rule in ipairs(NAME_RULES) do + if dhcpname:match(rule.rule) then + local dtype = {["c"] = 0, ["p"] = 0, ["n"] = ""} + local priority = 2 + dhcpiden = {} + if rule["type"] then + dtype = rule["type"] + end + if rule.priority then + priority = tonumber(rule.priority) + end + dhcpiden["name"] = rule.company + dhcpiden["icon"] = rule.icon + dhcpiden["type"] = dtype + dhcpiden["priority"] = priority + break + end + end + end + if maciden and dhcpiden then + if maciden.priority < dhcpiden.priority then + return maciden + else + return dhcpiden + end + elseif not maciden and not dhcpiden then + return {["name"] = "", ["icon"] = "", ["type"] = {["c"] = 0, ["p"] = 0, ["n"] = ""}, ["priority"] = 2} + else + return maciden or dhcpiden + end +end diff --git a/Mi_Lua/xiaoqiang/XQEvent.lua b/Mi_Lua/xiaoqiang/XQEvent.lua new file mode 100644 index 0000000..cb366cd --- /dev/null +++ b/Mi_Lua/xiaoqiang/XQEvent.lua @@ -0,0 +1,10 @@ +module ("xiaoqiang.XQEvent", package.seeall) + +function lanIPChange(ip) + local DMZ = require("xiaoqiang.module.XQDMZModule") + local PortForward = require("xiaoqiang.module.XQPortForward") + local LanWanUtil = require("xiaoqiang.util.XQLanWanUtil") + DMZ.hookLanIPChangeEvent(ip) + PortForward.hookLanIPChangeEvent(ip) + LanWanUtil.hookLanIPChangeEvent(ip) +end \ No newline at end of file diff --git a/Mi_Lua/xiaoqiang/XQLog.lua b/Mi_Lua/xiaoqiang/XQLog.lua new file mode 100644 index 0000000..66efb31 --- /dev/null +++ b/Mi_Lua/xiaoqiang/XQLog.lua @@ -0,0 +1,78 @@ +module ("xiaoqiang.XQLog", package.seeall) + +local posix = require("posix") + +--[[ + 0 mergency: system is unusable + 1 Alert: action must be taken immediately + 2 Critical: critical conditions + 3 Error: error conditions + 4 Warning: warning conditions + 5 Notice: normal but significant condition + 6 Informational: informational messages + 7 Debug: debug-level messages +]]-- +function log(...) + local priority = arg[1] + if priority and tonumber(priority) and tonumber(priority) >= 0 and tonumber(priority) <= 7 then + local util = require("luci.util") + posix.openlog("luci","np",LOG_USER) + for i = 2, arg.n do + posix.syslog(priority, util.serialize_data(arg[i])) + end + posix.closelog() + end +end + +--[[ + ctype: + 0 none + 1 instant +]]-- + +KEY_GEL_USE = "gel_use" +KEY_REBOOT = "gel_restart_soft_count" +KEY_DETECT_ERROR = "network_detect_error" + +KEY_VALUE_NETWORK_PPPOE = "network_method_pppoe" +KEY_VALUE_NETWORK_DHCP = "network_method_dhcp" +KEY_VALUE_NETWORK_STATIC = "network_method_static" + +KEY_GEL_INIT_ANDROID = "gel_init_android" +KEY_GEL_INIT_IOS = "gel_init_ios" +KEY_GEL_INIT_OTHER = "gel_init_other" + +KEY_DISKSLEEP_OPEN = "disk_sleep_open" +KEY_DISKSLEEP_CLOSE = "disk_sleep_close" + +KEY_FUNC_PPTP = "function_pptp_web" +KEY_FUNC_L2TP = "function_l2tp_web" +KEY_FUNC_APPQOS = "function_appqos" +KEY_FUNC_MACCLONE = "function_clone" +KEY_FUNC_QOS = "function_qos" +KEY_FUNC_UPNP = "function_upnp" +KEY_FUNC_DMZ = "function_dmz" +KEY_FUNC_PLUGIN = "function_plugin" +KEY_FUNC_PORTFADD = "function_port_forwarding_add" +KEY_FUNC_RANGEFADD = "function_range_forwarding_add" +KEY_FUNC_PORTENABLE = "function_port_forwarding_active" +KEY_FUNC_WIRELESS_ACCESS = "function_wireless_access" +KEY_FUNC_WIRELESS_BLACK = "function_wireless_access_blacklist" +KEY_FUNC_WIRELESS_WHITE = "function_wireless_access_whitelist" +KEY_FUNC_2G_CHANNEL = "function_channel_2g" +KEY_FUNC_5G_CHANNEL = "function_channel_5g" +KEY_FUNC_2G_SIGNAL = "function_channel_2g_signal" +KEY_FUNC_5G_SIGNAL = "function_channel_5g_signal" +KEY_FUNC_NOFLUSHED = "function_hdd_hibernation" + +function check(ctype, key, value) + local statPoints + if ctype == 0 then + statPoints = "stat_points_none" + else + statPoints = "stat_points_instant" + end + posix.openlog("web","np",LOG_USER) + posix.syslog(6, statPoints.." "..key.."="..tostring(value)) + posix.closelog() +end diff --git a/Mi_Lua/xiaoqiang/XQPreference.lua b/Mi_Lua/xiaoqiang/XQPreference.lua new file mode 100644 index 0000000..972671f --- /dev/null +++ b/Mi_Lua/xiaoqiang/XQPreference.lua @@ -0,0 +1,32 @@ +--[[ +Xiaoqiang Preference - Configuration +author : Afei +]]-- + +local util = require "luci.util" +module("xiaoqiang.XQPreference",package.seeall) + +function get(key, defaultValue, config) + require "luci.model.uci" + if not config then + config = "xiaoqiang" + end + local cursor = luci.model.uci.cursor(); + local value = cursor:get(config, "common", key) + return value or defaultValue; +end + +function set(key, value, config) + require "luci.model.uci" + if not config then + config = "xiaoqiang" + end + local cursor = luci.model.uci.cursor(); + if value == nil then + value = "" + end + cursor:set(config, "common", key, value) + cursor:save(config) + return cursor:commit(config) +end + diff --git a/Mi_Lua/xiaoqiang/XQPushHelper.lua b/Mi_Lua/xiaoqiang/XQPushHelper.lua new file mode 100644 index 0000000..54c086a --- /dev/null +++ b/Mi_Lua/xiaoqiang/XQPushHelper.lua @@ -0,0 +1,415 @@ +module ("xiaoqiang.XQPushHelper", package.seeall) + +local bit = require("bit") +local Json = require("json") + +local XQLog = require("xiaoqiang.XQLog") +local XQPreference = require("xiaoqiang.XQPreference") +local XQFunction = require("xiaoqiang.common.XQFunction") +local XQConfigs = require("xiaoqiang.common.XQConfigs") +local XQSysUtil = require("xiaoqiang.util.XQSysUtil") + +WIFI_CLEAR = false + +PUSH_DEFAULT_MESSAGE_TITLE = "新消息" +PUSH_DEFAULT_MESSAGE_DESCRIPTION = "您有一条新消息" + +PUSH_MESSAGE_TITLE = { + "系统升级", + "备注设备上线", + "陌生设备上线", + "所有WiFi设备离线", + "下载完成", + "智能场景", + "网络检测", + "加速相关", + "%s有更新,请升级!" +} + +PUSH_MESSAGE_DESCRIPTION = { + "路由器已经升级到最新版", + "备注设备上线", + "陌生设备上线", + "所有WiFi设备离线", + "全部下载任务已经完成", + "智能场景已经完成", + "网络检测已经完成", + "加速提醒", + "发现新版本%s(%s)" +} + +function _formatStr(str) + local str = string.gsub(str,"\"","\\\"") + str = string.gsub(str, ";", "\\;") + str = string.gsub(str, "&", "\\&") + return str:gsub(" ","") +end + +function _parserFlag(flag) + local result = { + ["f"] = false, + ["p"] = true + } + local flag = tonumber(flag) + if flag then + if bit.band(flag, 0x01) == 0x01 then + result.p = true + else + result.p = false + end + if bit.band(flag, 0x02) == 0x02 then + result.f = true + else + result.f = false + end + end + return result +end + +function _parserPushType(ptype) + local flag = "0x01" + local ptype = tostring(ptype) + if ptype then + flag = XQPreference.get(ptype, "0x01", "push") + end + return _parserFlag(flag) +end + +function _doPush(payload, title, description, ptype) + if not payload or not title or not description then + return + end + payload = _formatStr(payload) + local pushtype = "1" + if ptype then + pushtype = tostring(ptype) + end + os.execute(string.format("pushClient %s %s %s %s", payload, title, description, pushtype)) +end + +function _hookSysUpgraded() + local XQSysUtil = require("xiaoqiang.util.XQSysUtil") + local ver = XQSysUtil.getRomVersion() + local res = _parserPushType(1) + local payload = { + ["type"] = 1, + ["feed"] = res.f, + ["push"] = res.p, + ["ver"] = ver + } + _doPush(Json.encode(payload), PUSH_MESSAGE_TITLE[1], PUSH_MESSAGE_DESCRIPTION[1]) +end + +function _hookWifiConnect(mac) + if XQFunction.isStrNil(mac) then + return + else + mac = XQFunction.macFormat(mac) + end + local uci = require("luci.model.uci").cursor() + local mackey = mac:gsub(":", "") + local history = uci:get("devicelist", "history", mackey) + if not history then + uci:set("devicelist", "history", mackey, 1) + uci:commit("devicelist") + end + + local XQDeviceUtil = require("xiaoqiang.util.XQDeviceUtil") + local dhcpInfo = XQDeviceUtil.getDHCPDict()[mac] or {} + + if not history and XQFunction.isStrNil(dhcpInfo.name) then + os.execute("sleep 5") + dhcpInfo = XQDeviceUtil.getDHCPDict()[mac] or {} + end + + if not history and not XQFunction.isStrNil(dhcpInfo.name) then + local dhcpname = string.lower(dhcpInfo.name) + if dhcpname:match("^miwifi%-r1c") then + local payload = { + ["type"] = 23, + ["name"] = "小米路由器mini" + } + _doPush(Json.encode(payload), "中继成功", "中继成功") + return + elseif dhcpname:match("^miwifi%-r1d") or dhcpname:match("^miwifi%-r2d") then + local payload = { + ["type"] = 23, + ["name"] = "小米路由器" + } + _doPush(Json.encode(payload), "中继成功", "中继成功") + return + end + end + + if not history then + local XQEquipment = require("xiaoqiang.XQEquipment") + local XQDBUtil = require("xiaoqiang.util.XQDBUtil") + local device = XQDBUtil.fetchDeviceInfo(mac) + if device and XQFunction.isStrNil(device.nickname) then + local res = _parserPushType(3) + local name = dhcpInfo.name + if name and string.lower(name):match("android-%S+") and #name > 12 then + name = name:sub(1, 12) + end + if XQFunction.isStrNil(name) then + name = mac + else + local iden = XQEquipment.identifyDevice(mac, name) + if (iden["type"].c == 2 and iden["type"].p == 6) + or (iden["type"].c == 3 and iden["type"].p == 2) + or (iden["type"].c == 3 and iden["type"].p == 7) then + return + end + if iden["type"].c ~= 0 then + name = iden["type"].n + end + end + local payload = { + ["type"] = 3, + ["feed"] = res.f, + ["push"] = res.p, + ["mac"] = mac, + ["name"] = name + } + _doPush(Json.encode(payload), PUSH_MESSAGE_TITLE[3], PUSH_MESSAGE_DESCRIPTION[3]) + XQDBUtil.saveDeviceInfo(mac,dhcpInfo.name,"","","") + XQLog.log(6, "New Device Connect.", dhcpInfo) + end + end + --[[ + WIFI_CLEAR = false + local XQDBUtil = require("xiaoqiang.util.XQDBUtil") + local XQDeviceUtil = require("xiaoqiang.util.XQDeviceUtil") + local device = XQDBUtil.fetchDeviceInfo(mac) + local dhcpInfo = XQDeviceUtil.getDHCPDict()[mac] or {} + if device then + if device.mac and not XQFunction.isStrNil(device.nickname) then + local res = _parserPushType(2) + local payload = { + ["type"] = 2, + ["feed"] = res.f, + ["push"] = res.p, + ["mac"] = mac, + ["name"] = device.nickname + } + _doPush(Json.encode(payload), PUSH_MESSAGE_TITLE[2], PUSH_MESSAGE_DESCRIPTION[2]) + XQLog.log(6, "Special Device Connect.", device) + elseif device.mac then + XQLog.log(6, "Device Connect.", device) + else + local res = _parserPushType(3) + local name = dhcpInfo.name + if name and string.lower(name):match("android-%S+") and #name > 12 then + name = name:sub(1, 12) + end + if XQFunction.isStrNil(name) then + name = mac + end + local payload = { + ["type"] = 3, + ["feed"] = res.f, + ["push"] = res.p, + ["mac"] = mac, + ["name"] = name + } + XQDBUtil.saveDeviceInfo(mac,dhcpInfo.name,"","","") + _doPush(Json.encode(payload), PUSH_MESSAGE_TITLE[3], PUSH_MESSAGE_DESCRIPTION[3]) + XQLog.log(6, "New Device Connect.", dhcpInfo) + end + end + ]]-- +end + +function _hookWifiDisconnect(mac) + if XQFunction.isStrNil(mac) then + return + else + mac = XQFunction.macFormat(mac) + end + XQLog.log(6, "Device Disconnet:"..mac) + local XQWifiUtil = require("xiaoqiang.util.XQWifiUtil") + local count = #XQWifiUtil.getWifiConnectDeviceList(1) + #XQWifiUtil.getWifiConnectDeviceList(2) + if count == 0 and not WIFI_CLEAR then + local res = _parserPushType(4) + local payload = { + ["type"] = 4, + ["feed"] = res.f, + ["push"] = res.p + } + _doPush(Json.encode(payload), PUSH_MESSAGE_TITLE[4], PUSH_MESSAGE_DESCRIPTION[4]) + XQLog.log(6, "WiFi clear") + WIFI_CLEAR = true + end +end + +function _hookAllDownloadFinished() + XQLog.log(6, "All download finished") + local res = _parserPushType(5) + local payload = { + ["type"] = 5, + ["feed"] = res.f, + ["push"] = res.p + } + _doPush(Json.encode(payload), PUSH_MESSAGE_TITLE[5], PUSH_MESSAGE_DESCRIPTION[5]) +end + +function _hookIntelligentScene(name,actions) + local sname = name + if XQFunction.isStrNil(sname) then + sname = "" + end + XQLog.log(6, "Intelligent Scene:"..name.." finished!") + local res = _parserPushType(6) + local payload = { + ["type"] = 6, + ["feed"] = res.f, + ["push"] = res.p, + ["name"] = name, + ["actions"] = actions + } + _doPush(Json.encode(payload), PUSH_MESSAGE_TITLE[6], PUSH_MESSAGE_DESCRIPTION[6]) +end + +function _hookDetectFinished(lan, wan) + if lan and wan then + XQLog.log(6, "network detect finished!") + local res = _parserPushType(7) + local payload = { + ["type"] = 7, + ["feed"] = res.f, + ["push"] = res.p, + ["lan"] = lan, + ["wan"] = wan + } + _doPush(Json.encode(payload), PUSH_MESSAGE_TITLE[7], PUSH_MESSAGE_DESCRIPTION[7]) + end +end + +function _hookCachecenterEvent(hitcount, timesaver) + if hitcount and timesaver then + XQLog.log(6, "cachecenter event!") + local res = _parserPushType(13) + local payload = { + ["type"] = 13, + ["feed"] = res.f, + ["push"] = res.p, + ["hitcount"] = hitcount, + ["timesaver"] = timesaver + } + _doPush(Json.encode(payload), PUSH_MESSAGE_TITLE[8], PUSH_MESSAGE_DESCRIPTION[8]) + end +end + +function _hookDownloadEvent(count) + if tonumber(count) then + XQLog.log(6, "download event!") + local payload = { + ["type"] = 17, + ["count"] = tonumber(count) + } + _doPush(Json.encode(payload), "下载完成", "下载完成") + end +end + +function _hookUploadEvent(count) + if tonumber(count) then + XQLog.log(6, "upload event!") + local payload = { + ["type"] = 18, + ["count"] = tonumber(count) + } + _doPush(Json.encode(payload), "上传完成", "上传完成") + end +end + +function _hookADFilterEvent(page, all) + if tonumber(page) and tonumber(all) then + XQLog.log(6, "upload event!") + local payload = { + ["type"] = 19, + ["page"] = tonumber(page), + ["all"] = tonumber(all) + } + _doPush(Json.encode(payload), "广告过滤", "广告过滤") + end +end + +function _hookDefault(data) + XQLog.log(6, "Unknown Feed") + local res = _parserPushType(999) + local payload = { + ["type"] = 999, + ["feed"] = res.f, + ["push"] = res.p, + ["data"] = data + } + _doPush(Json.encode(payload), PUSH_DEFAULT_MESSAGE_TITLE, PUSH_DEFAULT_MESSAGE_DESCRIPTION) +end + +function _hookNewRomVersionDetected(version) + XQLog.log(6, "New ROM version detected") + local res = _parserPushType(8) + local routerName = XQPreference.get(XQConfigs.PREF_ROUTER_NAME, "") + local _romChannel = XQSysUtil.getChannel() + local payload = { + ["type"] = 14, + ["feed"] = res.f, + ["push"] = res.p, + ["name"] = routerName, + ["version"] = version, + ["channel"] = _romChannel + } + local title = string.format(PUSH_MESSAGE_TITLE[9], routerName) + local romChannel = "开发版" + if _romChannel == "current" then + romChannel = "内测版" + end + if _romChannel == "release" then + romChannel = "稳定版" + end + local description = string.format(PUSH_MESSAGE_DESCRIPTION[9], version, romChannel) + _doPush(Json.encode(payload), title, description) +end + +function push_request_lua(payload) + local ptype = tonumber(payload.type) + if ptype == 1 then + _hookWifiConnect(payload.data.mac) + elseif ptype == 2 then + _hookWifiDisconnect(payload.data.mac) + elseif ptype == 3 then + _hookSysUpgraded() + elseif ptype == 4 then + _hookAllDownloadFinished() + elseif ptype == 5 then + _hookIntelligentScene(payload.data.name,payload.data.list) + elseif ptype == 6 then + _hookDetectFinished(payload.data.lan, payload.data.wan) + elseif ptype == 7 then + _hookCachecenterEvent(payload.data.hit_count, payload.data.timesaver) + elseif ptype == 8 then + _hookNewRomVersionDetected(payload.data.version) + elseif ptype == 9 then + _hookDownloadEvent(payload.data.count) + elseif ptype == 10 then + _hookUploadEvent(payload.data.count) + elseif ptype == 11 then + _hookADFilterEvent(payload.data.filter_page, payload.data.filter_all) + else + _hookDefault(payload.data) + end + return true +end + +-- +-- type:{1,2,3...} +-- data:{...} +-- +function push_request(payload) + if XQFunction.isStrNil(payload) then + return false + end + XQLog.log(6,"Push request:",payload) + local payload = Json.decode(payload) + return push_request_lua(payload) +end \ No newline at end of file diff --git a/Mi_Lua/xiaoqiang/XQVersion.lua b/Mi_Lua/xiaoqiang/XQVersion.lua new file mode 100644 index 0000000..dce6fdf --- /dev/null +++ b/Mi_Lua/xiaoqiang/XQVersion.lua @@ -0,0 +1,10 @@ +module "xiaoqiang.XQVersion" + +webVersion = "0.0.3" +webDefaultHost = "192.168.31.1" +pcClientRouter = "http://bigota.miwifi.com/xiaoqiang/client/xqpc_client.exe" +pcClientServer = "http://bigota.miwifi.com/xiaoqiang/client/xqpc_client.exe" +macClientRouter = "http://bigota.miwifi.com/xiaoqiang/client/xqmac_client.dmg" +macClientServer = "http://bigota.miwifi.com/xiaoqiang/client/xqmac_client.dmg" +androidClientRouter = "/client/xqapp.apk" +androidClientServer = "http://bigota.miwifi.com/xiaoqiang/client/xqapp.apk" diff --git a/Mi_Lua/xiaoqiang/common/XQConfigs.lua b/Mi_Lua/xiaoqiang/common/XQConfigs.lua new file mode 100644 index 0000000..46418e3 --- /dev/null +++ b/Mi_Lua/xiaoqiang/common/XQConfigs.lua @@ -0,0 +1,220 @@ +module ("xiaoqiang.common.XQConfigs", package.seeall) + +-- SERVER_CONFIG : +-- 0 : online +-- 1 : staging +-- 2 : preview +SERVER_CONFIG = 0 +SERVER_CONFIG_ONLINE_URL = "http://api.gorouter.info" +SERVER_CONFIG_STAGING_URL = "http://api.staging.gorouter.info" +SERVER_CONFIG_PREVIEW_URL = "http://api.preview.gorouter.info" + +PASSPORT_CONFIG_ONLINE_URL = "https://account.xiaomi.com/pass/serviceLogin" +PASSPORT_CONFIG_PREVIEW_URL = "http://account.preview.n.xiaomi.net/pass/serviceLogin" +XQ_SERVER_ONLINE_STS_URL = "https://www.gorouter.info/sts" +XQ_SERVER_STAGING_STS_URL = "https://www.staging.gorouter.info/sts" +PASSPORT_LOGOUT_ONLINE_URL = "https://account.xiaomi.com/pass/logout" +PASSPORT_LOGOUT_PREVIEW_URL = "http://account.preview.n.xiaomi.net/pass/logout" +XQ_SERVER_ONLINE_API_URL = "https://www.gorouter.info" +XQ_SERVER_STAGING_API_URL = "https://www.staging.gorouter.info" + +-- Statistics +ARP_LIST_UI_FILEPATH = "/tmp/activate.arp.list.ui" +NIC_LIST_UI_FILEPATH = "/tmp/activate.nic.list.ui" + +-- Config/log file +CONFIG_ZIP_FILEPATH = "/tmp/config.zip" +LOG_ZIP_FILEPATH = "/tmp/log.zip" +PPP_LOG_FILEPATH = "/var/log/ppp.log" + +PREF_IS_INITED = "INITTED" +PREF_IS_PASSPORT_BOUND = "PASSPORT_BOUND" +PREF_ROUTER_NAME = "ROUTER_NAME" +PREF_WAN_SPEED_HISTORY = "WAN_SPEED_HISTORY" +PREF_PASSPORT_BOUND_UUID = "PASSPORT_UUID" +PREF_UPGRADE_INFO = "UPGRADE_INFO" +PREF_WPS_TIMESTAMP = "WPS_TIMESTAMP" +PREF_ROUTER_NAME_PENDING = "ROUTER_NAME_PENDING" +PREF_BOUND_USERINFO = "BOUND_USER_INFO" +PREF_ROM_FULLSIZE = "ROM_FULLSIZE" +PREF_PPPOE_NAME = "PPPOE_NAME" +PREF_PPPOE_PASSWORD = "PPPOE_PASSWORD" +PREF_ROM_DOWNLOAD_URL = "ROM_DOWNLOAD_URL" +PREF_ROM_UPLOAD_URL = "ROM_UPLOAD_URL" +PREF_PAUSED_IDS = "PAUSED_IDS" +PREF_TIMESTAMP = "TIMESTAMP" +PREF_ROM_DOWNLOAD_ID = "ROM_DOWNLOAD_ID" + +FORK_RESTART_WIFI = "sleep 4; /sbin/wifi >/dev/null 2>/dev/null; /etc/init.d/minidlna restart; /etc/init.d/samba restart; /usr/bin/gettraffic flush_wl_dev >/dev/null 2>/dev/null" +FORK_RESET_ALL = "env -i sleep 4 && nvram set restore_defaults=1 && nvram commit && reboot & >/dev/null 2>/dev/null" +FORK_RESTART_ROUTER = "/usr/sbin/phyhelper stop;sleep 4; reboot" +FORK_SHUTDOWN_ROUTER = "sleep 4; /usr/sbin/uhbn 3" +FORK_RESTART_DNSMASQ = "sleep 2; /etc/init.d/dnsmasq restart" +RESTART_MAC_FILTER = "/bin/sh /etc/firewall.macfilter" + +-- Device name file +DEVICE_NAMES_FILE = "/etc/app/device_names" + +-- DHCP lease file +DHCP_LEASE_FILEPATH = "/var/dhcp.leases" +-- DHCP deny list file +DHCP_DENYLIST_FILEPATH = "/etc/config/firewall.mac.list" + +-- Wan status +WAN_MONITOR_STAT_FILEPATH = "/tmp/wan.monitor.stat" + +-- Rom Version +XQ_ROM_VERSION_FILEPATH = "/usr/share/xiaoqiang/xiaoqiang_version" +-- Log file +XQ_LOG_JSON_FILEPATH = "/tmp/log.json" +XQ_CONFIG_JSON_FILEPATH = "/tmp/config.json" +-- Wifi Passport Error +XQ_WIFIPWDERROR_FILEPATH = "/tmp/wifi_error_xxxx" +-- Change log +XQ_CHANGELOG_FILEPATH = "/usr/share/xiaoqiang/changelog" + +-- Download Rom file +ROM_CACHE_FILEPATH = "/tmp/rom.bin" +ROM_DISK_CACHE_FILEPATH = "/userdisk/rom.bin" +-- Upload Rom file +CROM_CACHE_FILEPATH = "/tmp/customrom.bin" +CROM_DISK_CACHE_FILEPATH = "/userdisk/upload/customrom.bin" +USERDISK_UPLOAD_DIR = "/userdisk/upload/" +-- Download Rom dir +USERDISK_DOWNLOAD_DIR = "/userdisk/download/" + +-- Download Uboot file +UBOOT_CACHE_FILEPATH = "/tmp/uboot.bin" + +-- OUI File: from http://standards.ieee.org/develop/regauth/oui/public.html +OUI_ZIP_FILEPATH = "/usr/share/xiaoqiang/oui.zip" +OUI_FILEPATH = "/tmp/oui" + +-- Version Info +XQ_ROM_VERSION = "uci get /usr/share/xiaoqiang/xiaoqiang_version.version.ROM" +XQ_CHANNEL = "uci get /usr/share/xiaoqiang/xiaoqiang_version.version.CHANNEL" +XQ_HARDWARE = "uci get /usr/share/xiaoqiang/xiaoqiang_version.version.HARDWARE" +XQ_CFE_VERSION = "uci get /usr/share/xiaoqiang/xiaoqiang_version.version.UBOOT" +XQ_KERNEL_VERSION = "uci get /usr/share/xiaoqiang/xiaoqiang_version.version.LINUX" +XQ_RAMFS_VERSION = "uci get /usr/share/xiaoqiang/xiaoqiang_version.version.RAMFS" +XQ_SQAFS_VERSION = "uci get /usr/share/xiaoqiang/xiaoqiang_version.version.SQAFS" +XQ_ROOTFS_VERSION = "uci get /usr/share/xiaoqiang/xiaoqiang_version.version.ROOTFS" +XQ_DEVICE_ID = "uci get /etc/config/messaging.deviceInfo.DEVICE_ID" + +XQ_CUT_IMAGE = "cd /tmp;multipartcutter -v -R -f " +XQ_VERIFY_IMAGE = "cd /tmp;mkxqimage -x " + +OPEN_WPS = "wps pbc" +GET_WPS_STATUS = "wps status" +GET_WPS_CONMAC = "wps stamac" +CLOSE_WPS = "wps stop" +GET_DEFAULT_MACADDRESS = "getmac" +GET_NVRAM_SN = "nvram get SN" +GET_BDATA_SN = "bdata get SN" +NVRAM_SET_UPGRADED = "nvram set flag_upgrade_push=1; nvram commit" + +NGINX_CACHE_START = "/usr/sbin/sysapi TRAFFIC_CTL set NGINX_CACHE=on" +NGINX_CACHE_STOP = "/usr/sbin/sysapi TRAFFIC_CTL set NGINX_CACHE=off" +NGINX_CACHE_STATUS = "/usr/sbin/sysapi TRAFFIC_CTL get NGINX_CACHE" + +SET_LAN_BLACKLIST = "/usr/sbin/sysapi macfilter set lanmode=blacklist" +SET_LAN_WHITELIST = "/usr/sbin/sysapi macfilter set lanmode=whitelist" +SET_WAN_BLACKLIST = "/usr/sbin/sysapi macfilter set wanmode=blacklist" +SET_WAN_WHITELIST = "/usr/sbin/sysapi macfilter set wanmode=whitelist" +SET_ADMIN_BLACKLIST = "/usr/sbin/sysapi macfilter set admin=blacklist" +SET_ADMIN_WHITELIST = "/usr/sbin/sysapi macfilter set admin=whitelist" +GET_LAN_MODE = "/usr/sbin/sysapi macfilter get lanmode" +GET_WAN_MODE = "/usr/sbin/sysapi macfilter get wanmode" +GET_ADMIN_MODE = "/usr/sbin/sysapi macfilter get adminmode" + +LAMP_CREATE_SANDBOX = "/opt/lampmanager/create_sandbox.sh" +LAMP_IS_SANDBOX_CREATED = "/opt/lampmanager/is_sandbox_created.sh" +LAMP_MOUNT_THINGS = "/opt/lampmanager/mount_things.sh" +LAMP_UMOUNT_THINGS = "/opt/lampmanager/unmount_things.sh" +LAMP_ARE_THINGS_MOUNTED = "/opt/lampmanager/are_things_mounted.sh" +LAMP_START_DROPBEAR = "/opt/lampmanager/start_dropbear.sh" +LAMP_STOP_DROPBEAR = "/opt/lampmanager/stop_dropbear.sh" +LAMP_IS_DROPBEAR_STARTED = "/opt/lampmanager/is_dropbear_started.sh" + +CPU_TEMPERATURE = "/usr/sbin/readtmp" +SIMPLE_NETWORK_DETECT = "/usr/sbin/networkdt -s " +SIMPLE_NETWORK_NOLOG_DETECT = "/usr/sbin/networkdt -s -n " +FULL_NETWORK_DETECT = "/usr/sbin/networkdt " +WIFI_CHANNEL_24 = "wl -i wl1 chanspecs" +WIFI_CHANNEL_50 = "wl -i wl0 chanspecs" +WIFI24_WORK_CHANNEL = "wl -i wl1 chanspec | awk '{print $1}'" +WIFI50_WORK_CHANNEL = "wl -i wl0 chanspec | awk '{print $1}'" + +GET_WAN_DEV = "ip route list 0/0 | grep -v tap | grep -v metric | awk '{print $5}'" +FLASH_EXECUTION_CHECK = "/bin/flash_check.sh" +FLASH_PID_TMP = "/tmp/pid_xxxx" +CRONTAB_PID_TMP = "/tmp/crontab_pid_xxxx" + +CRONTAB_ROM_CHECK = [[ps w | grep crontab_rom.sh | grep -v "grep" | wc -l]] +CROM_FLASH_CHECK = [[ps w | grep flash | grep customrom.bin | grep -v "grep" | wc -l]] +DROM_FLASH_CHECK = [[ps w | grep flash | grep rom.bin | grep -v "grep" | wc -l]] +REBOOT_CHECK = [[ps w | grep reboot | grep -v "grep" | wc -l]] + +UPGRADE_LOCK_FILE = "/tmp/upgrade_lock" +UPGRADE_STATUS = "cat "..UPGRADE_LOCK_FILE +UPGRADE_LOCK = "/bin/touch "..UPGRADE_LOCK_FILE +UPGRADE_UNLOCK = "/bin/rm "..UPGRADE_LOCK_FILE +UPGRADE_LOCK_CHECK = "/bin/ls -l "..UPGRADE_LOCK_FILE +UPGRADE_PID = "cat "..CRONTAB_PID_TMP +UPGRADE_LUA_PID = [[ps w | grep checkupgrade.lua | grep -v "grep" | awk '{print $1}']] + +GET_CPU_CHIPPKG = "cat /proc/cpuinfo | grep b_chippkg | awk '{print $3}'" + +DOWNLOAD_RESOURCE_CHECK = "wget -t3 -T10 --spider " + +-- 1K-blocks +AVAILABLE_MEMERY = [[df -k | grep /tmp$ | awk '{print $4}']] +AVAILABLE_DISK = [[df -k | grep /userdisk$ | awk '{print $4}']] +DISK_SPACE = [[df -k | grep /userdisk$ | awk '{print $2}']] + +DEVICE_STATISTICS_LIST_LIMIT = 10 + +-- CPU Avg +CPU_LOAD_AVG = "/usr/sbin/getstat.lua|cut -d'%' -f1" +MEMERY_USAGE = "free 2>/dev/null|awk '/Mem/{print substr($3/$2,0,4)}'" +WAN_LINK = "et robord 0x01 0x00 2>/dev/null|awk -F':' '/port 4/{print$2}'" +WAN_UP = "cat /tmp/wan.monitor.stat | grep WANLINKSTAT=UP | wc -l" + +-- Update led flash alert +UPDATE_LED_FLASH_ALERT_ENABLE = "updateledfliker" +UPDATE_LED_FLASH_ALERT_DISABLE = "killupdateled" + +VPN_ENABLE = "/usr/sbin/vpn.lua up" +VPN_DISABLE = "/usr/sbin/vpn.lua down" +VPN_STATUS = "/usr/sbin/vpn.lua status" +RM_VPNSTATUS_FILE = "/bin/rm /tmp/vpn.stat.msg.last >/dev/null 2>/dev/null" + +GPIO_VALUE = "gpio %s | awk -F': <' '{print$2}'| awk -F'>' '{print$1}'" + +-- UPnP +UPNP_STATUS = "/etc/init.d/miniupnpd enabled" +UPNP_ENABLE = "/etc/init.d/miniupnpd enable ; /etc/init.d/miniupnpd start" +UPNP_DISABLE = "/etc/init.d/miniupnpd stop ; /etc/init.d/miniupnpd disable" +UPNP_LEASE_FILE = "uci get upnpd.config.upnp_lease_file" + +-- QoS +QOS_APPSL_ENABLE = "/etc/init.d/app-tc.d on" +QOS_APPSL_DISABLE = "/etc/init.d/app-tc.d off" +QOS_APPSL_RELOAD = "/etc/init.d/app-tc.d restart" + +UPGRADE_INFO_CACHE = "upgrade_info_cache" +UPGRADE_INFO_EXPIRE = 600 + +THRIFT_TUNNEL_TO_DATACENTER = "thrifttunnel 0 '%s'" +THRIFT_TUNNEL_TO_SMARTHOME = "thrifttunnel 1 '%s'" +THRIFT_TUNNEL_TO_SMARTHOME_CONTROLLER = "thrifttunnel 2 '%s'" +THRIFT_TO_MQTT_IDENTIFY_DEVICE = "thrifttunnel 3 ''" +THRIFT_TO_MQTT_GET_SN = "thrifttunnel 4 ''" +THRIFT_TO_MQTT_GET_DEVICEID = "thrifttunnel 5 ''" +THRIFT_TUNNEL_TO_MIIO = "thrifttunnel 6 '%s'" +THRIFT_TUNNEL_TO_YEELINK = "thrifttunnel 7 '%s'" + +TUNNEL_TOOL = "/opt/filetunnel/tunneltool --payload '%s'" + +WIIF_LOG_TMP_FILEPATH = "/tmp/wifi.log" +WIFI_LOG_COLLECTION = "/sbin/wifi_analyze.sh >> "..WIIF_LOG_TMP_FILEPATH diff --git a/Mi_Lua/xiaoqiang/common/XQFunction.lua b/Mi_Lua/xiaoqiang/common/XQFunction.lua new file mode 100644 index 0000000..a6368c4 --- /dev/null +++ b/Mi_Lua/xiaoqiang/common/XQFunction.lua @@ -0,0 +1,345 @@ +module ("xiaoqiang.common.XQFunction", package.seeall) + +local XQConfigs = require("xiaoqiang.common.XQConfigs") + +--[[ +@param mac: mac address +@return XX:XX:XX:XX:XX:XX +]]-- +function macFormat(mac) + if mac then + return string.upper(string.gsub(mac,"-",":")) + else + return "" + end +end + +function isStrNil(str) + return (str == nil or str == "") +end + +function parseEnter2br(str) + if (str ~= nil) then + str = str:gsub("\r\n", "
") + str = str:gsub("\r", "
") + str = str:gsub("\n", "
") + end + return str +end +--flag2 +function forkExec(command) + local Nixio = require("nixio") + local pid = Nixio.fork() + if pid > 0 then + return + elseif pid == 0 then + Nixio.chdir("/") + local null = Nixio.open("/dev/null", "w+") + if null then + Nixio.dup(null, Nixio.stderr) + Nixio.dup(null, Nixio.stdout) + Nixio.dup(null, Nixio.stdin) + if null:fileno() > 2 then + null:close() + end + end + Nixio.exec("/bin/sh", "-c", command) + end +end + +function doPrint(content) + if type(content) == "table" then + for k,v in pairs(content) do + if type(v) == "table" then + print("<"..k..": ") + doPrint(v) + print(">") + else + print("["..k.." : "..tostring(v).."]") + end + end + else + print(content) + end +end + +function forkRestartWifi() + forkExec(XQConfigs.FORK_RESTART_WIFI) +end + +function forkReboot() + forkExec(XQConfigs.FORK_RESTART_ROUTER) +end + +function forkShutdown() + forkExec(XQConfigs.FORK_SHUTDOWN_ROUTER) +end + +function forkResetAll() + forkExec(XQConfigs.FORK_RESET_ALL) +end + +function forkRestartDnsmasq() + forkExec(XQConfigs.FORK_RESTART_DNSMASQ) +end + +function forkFlashRomFile(filePath) + forkExec("flash.sh "..filePath) +end +--flag1 +function forkShutdownAndRebootWithDelay(m1, m2) + local min1 = tonumber(m1) + local min2 = tonumber(m2) + if min1 and min2 and (min1 ~=0 or min2 ~= 0) then + local cmd + if min1 > 0 and min2 > 0 then + cmd = string.format("sleep %s ; /usr/sbin/uhbn 2 %s", tostring(60*min1), tostring(min2)) + end + if min1 == 0 and min2 > 0 then + cmd = string.format("sleep 4 ; /usr/sbin/uhbn 2 %s", tostring(min2)) + end + if min1 > 0 and min2 == 0 then + cmd = string.format("sleep %s ; /usr/sbin/uhbn 3", tostring(60*min1)) + end + forkExec(cmd) + end +end + +function syncRestartMacFilter() + os.execute(XQConfigs.RESTART_MAC_FILTER) +end + +function closeWebInitRDR() + os.execute("/usr/sbin/sysapi webinitrdr set off") +end + +function getTime() + return os.date("%Y-%m-%d--%X",os.time()) +end + +function hzFormat(hertz) + local suff = {"Hz", "KHz", "MHz", "GHz", "THz"} + for i=1, 5 do + if hertz > 1024 and i < 5 then + hertz = hertz / 1024 + else + return string.format("%.2f %s", hertz, suff[i]) + end + end +end + +function byteFormat(byte) + local suff = {"B", "KB", "MB", "GB", "TB"} + for i=1, 5 do + if byte > 1024 and i < 5 then + byte = byte / 1024 + else + return string.format("%.2f %s", byte, suff[i]) + end + end +end + +function checkSSID(ssid) + if isStrNil(ssid) then + return false + end + if string.match(ssid,"^%w[%w_.-]*%w$") or string.match(ssid,"^%w$") then + return true + else + return false + end +end + +function sysLock() + return os.execute(XQConfigs.UPGRADE_LOCK) +end + +function sysUnlock() + return os.execute(XQConfigs.UPGRADE_UNLOCK) +end + +function sysLockStatus() + local LuciFs = require("luci.fs") + local status = LuciFs.access(XQConfigs.UPGRADE_LOCK_FILE) + if status then + return 1 + else + return 0 + end +end + +function ledFlashAlert(enable) + if enable then + forkExec(XQConfigs.UPDATE_LED_FLASH_ALERT_ENABLE) + else + os.execute(XQConfigs.UPDATE_LED_FLASH_ALERT_DISABLE) + end +end + +function getGpioValue(digit) + local LuciUtil = require("luci.util") + local gpio = LuciUtil.exec(string.format(XQConfigs.GPIO_VALUE,tostring(digit))) + if gpio then + return tonumber(LuciUtil.trim(gpio)) or 0 + end + return 0 +end + +function nvramGet(key, default) + if isStrNil(key) then + return default + end + local LuciUtil = require("luci.util") + local cmd = string.format("nvram get %s", key) + local value = LuciUtil.exec(cmd) + if value then + value = LuciUtil.trim(value) + return value + end + return default +end + +function nvramSet(key, value) + if isStrNil(key) then + return + end + local cmd + if isStrNil(value) then + cmd = string.format("nvram unset %s", key) + else + cmd = string.format("nvram set %s=%s", key, value) + end + os.execute(cmd) +end + +function nvramCommit() + os.execute("nvram commit") +end + +function getNetMode() + local uci = require("luci.model.uci").cursor() + local netmode = uci:get("xiaoqiang", "common", "NETMODE") + if isStrNil(netmode) then + netmode = nil + end + return netmode +end + +function setNetMode(mode) + local uci = require("luci.model.uci").cursor() + if mode then + uci:set("xiaoqiang", "common", "NETMODE", mode) + else + uci:delete("xiaoqiang", "common", "NETMODE") + end + uci:commit("xiaoqiang") +end + +function thrift_tunnel_to_datacenter(payload) + if isStrNil(payload) then + return nil + end + local json = require("cjson") + local luciutil = require("luci.util") + local XQCryptoUtil = require("xiaoqiang.util.XQCryptoUtil") + payload = XQCryptoUtil.binaryBase64Enc(payload) + local result = luciutil.trim(luciutil.exec(XQConfigs.THRIFT_TUNNEL_TO_DATACENTER % payload)) + if isStrNil(result) then + return nil + else + return json.decode(result) + end +end + +function thrift_to_mqtt_identify_device() + local LuciUtil = require("luci.util") + local cmd = XQConfigs.THRIFT_TO_MQTT_IDENTIFY_DEVICE + local d = LuciUtil.exec(cmd) + if isStrNil(d) then + d = "" + end + return d +end + +function thrift_to_mqtt_get_sn() + local LuciUtil = require("luci.util") + local cmd = XQConfigs.THRIFT_TO_MQTT_GET_SN + local serialNumber = LuciUtil.exec(cmd) + if isStrNil(serialNumber) then + serialNumber = "" + end + return serialNumber +end + +function thrift_to_mqtt_get_deviceid() + local LuciUtil = require("luci.util") + local cmd = XQConfigs.THRIFT_TO_MQTT_GET_DEVICEID + local deviceId = LuciUtil.exec(cmd) + if isStrNil(deviceId) then + deviceId = "" + end + return deviceId +end + +function thrift_tunnel_to_smarthome_controller(payload) + if isStrNil(payload) then + return nil + end + local json = require("json") + local LuciUtil = require("luci.util") + local XQCryptoUtil = require("xiaoqiang.util.XQCryptoUtil") + payload = XQCryptoUtil.binaryBase64Enc(payload) + local result = LuciUtil.exec(XQConfigs.THRIFT_TUNNEL_TO_SMARTHOME_CONTROLLER % payload) + if isStrNil(result) then + return nil + else + return json.decode(result) + end +end + +function _parse(hex, pre) + if hex == 0 then + return 0 + end + local value = 0 + for i=0, 7 do + local shift = bit.rshift(128, i) + local res = bit.band(hex, shift) + if res == 0 then + if hex - value == 0 then + if pre == 0 or pre == 2 then + return -1 + elseif pre == 1 then + return 2 + end + else + return -1 + end + end + value = value + shift + end + return 1 +end + +function checkMask(mask) + if isStrNil(mask) then + return false + end + if mask == "0.0.0.0" or mask == "255.255.255.255" then + return false + end + local LuciUtil = require("luci.util") + local LuciDatatypes = require("luci.cbi.datatypes") + if not LuciDatatypes.ipaddr(mask) then + return false + end + local maskarr = LuciUtil.split(mask, ".") + local pre = 1 + for _,value in ipairs(maskarr) do + pre = _parse(tonumber(value), pre) + if pre == -1 then + return false + end + end + return true +end diff --git a/Mi_Lua/xiaoqiang/module/XQAPModule.lua b/Mi_Lua/xiaoqiang/module/XQAPModule.lua new file mode 100644 index 0000000..502ebf7 --- /dev/null +++ b/Mi_Lua/xiaoqiang/module/XQAPModule.lua @@ -0,0 +1,26 @@ +module ("xiaoqiang.module.XQAPModule", package.seeall) + +local XQFunction = require("xiaoqiang.common.XQFunction") +local XQConfigs = require("xiaoqiang.common.XQConfigs") + +function enableLanAP() + local uci = require("luci.model.uci").cursor() + local ipaddr = uci:get("network", "lan", "ipaddr") + XQFunction.setNetMode("apmode") + local newipaddr = uci:get("network", "lan", "ipaddr") + if newipaddr ~= ipaddr then + XQFunction.forkExec("sleep 4; ap_mode.sh open") + return newipaddr + else + return nil + end +end + +function disableLanAP() + local uci = require("luci.model.uci").cursor() + local ipaddr = uci:get("network", "lan", "ipaddr") + XQFunction.setNetMode(nil) + os.execute("ap_mode.sh close") + XQFunction.forkExec("sleep 4; /etc/init.d/network restart") + return ipaddr +end \ No newline at end of file diff --git a/Mi_Lua/xiaoqiang/module/XQDDNS.lua b/Mi_Lua/xiaoqiang/module/XQDDNS.lua new file mode 100644 index 0000000..a299978 --- /dev/null +++ b/Mi_Lua/xiaoqiang/module/XQDDNS.lua @@ -0,0 +1,280 @@ +module ("xiaoqiang.module.XQDDNS", package.seeall) + +local XQFunction = require("xiaoqiang.common.XQFunction") +local XQConfigs = require("xiaoqiang.common.XQConfigs") + +--- noip noip.com +--- oray 花生壳 +local SERVICES = { + ["noip"] = { + ["service_name"] = "no-ip.com", + ["ip_url"] = "http://[USERNAME]:[PASSWORD]@dynupdate.no-ip.com/nic/update?hostname=[DOMAIN]&myip=[IP]" + }, + ["oray"] = { + ["service_name"] = "oray.com", + ["ip_url"] = "http://[USERNAME]:[PASSWORD]@ddns.oray.com:80/ph/update?hostname=[DOMAIN]&myip=[IP]" + }, + ["pubyun"] = { + ["service_name"] = "3322.org", + ["ip_url"] = "http://[USERNAME]:[PASSWORD]@members.3322.net/dyndns/update?hostname=[DOMAIN]&myip=[IP]" + }, + ["dyndnsorg"] = { + ["service_name"] = "dyndns.org", + ["ip_url"] = "http://[USERNAME]:[PASSWORD]@members.dyndns.org/nic/update?wildcard=ON&hostname=[DOMAIN]&myip=[IP]" + }, + ["dyndnsfr"] = { + ["service_name"] = "dyndns.fr", + ["ip_url"] = "http://[DOMAIN]:[PASSWORD]@dyndns.dyndns.fr/update.php?hostname=[DOMAIN]&myip=[IP]" + }, + ["dyndnspro"] = { + ["service_name"] = "dyndnspro.com", + ["ip_url"] = "http://[DOMAIN]:[PASSWORD]@dyndns.dyndnspro.com/update.php?hostname=[DOMAIN]&myip=[IP]" + }, + ["dynamicdomain"] = { + ["service_name"] = "dynamicdomain.net", + ["ip_url"] = "http://[DOMAIN]:[PASSWORD]@dyndns.dynamicdomain.net/update.php?hostname=[DOMAIN]&myip=[IP]" + }, + ["dyndnsit"] = { + ["service_name"] = "dyndns.it", + ["ip_url"] = "http://[USERNAME]:[PASSWORD]@dyndns.it/nic/update?hostname=[DOMAIN]&myip=[IP]" + }, + ["duckdns"] = { + ["service_name"] = "duckdns.org", + ["ip_url"] = "http://www.duckdns.org/update?domains=[DOMAIN]&token=[PASSWORD]&ip=[IP]" + }, + ["systemns"] = { + ["service_name"] = "system-ns.com", + ["ip_url"] = "http://system-ns.com/api?type=dynamic&domain=[DOMAIN]&command=set&token=[PASSWORD]&ip=[IP]" + } +} + +--- id +--- 1: noip +--- 2: oray 花生壳 +--- 3: 公云 +--- ... +local MAP = { + "noip", + "oray", + "pubyun", + "dyndnsorg", + "dyndnsfr", + "dyndnspro", + "dynamicdomain", + "dyndnsit", + "duckdns", + "systemns" +} + +function _serverId(server) + for id, ser in ipairs(MAP) do + if ser == server then + return id + end + end + return false +end + +function _ddnsRestart() + return os.execute("/usr/sbin/ddnsd reload") == 0 +end + +--- server: noip/oray +--- enable: 0/1 +function _saveConfig(server, enable, username, password, checkinterval, forceinterval, domain) + local uci = require("luci.model.uci").cursor() + local service = SERVICES[server] + if service and username and password and domain and checkinterval and forceinterval then + uci:foreach("ddns", "service", + function(s) + if s[".name"] ~= server and tonumber(s.enabled) == 1 then + uci:set("ddns", s[".name"], "enabled", 0) + end + end + ) + local section = { + ["enabled"] = enable, + ["interface"] = "wan", + ["service_name"] = service.service_name, + ["force_interval"] = forceinterval, + ["force_unit"] = "hours", + ["check_interval"] = checkinterval, + ["check_unit"] = "minutes", + ["username"] = username, + ["password"] = password, + ["ip_source"] = "network", + ["ip_url"] = service.ip_url, + ["domain"] = domain + } + uci:section("ddns", "service", server, section) + uci:commit("ddns") + return true + end + return false +end + +function _ddnsServerSwitch(server, enable) + local uci = require("luci.model.uci").cursor() + if XQFunction.isStrNil(server) then + return false + end + uci:foreach("ddns", "service", + function(s) + if s[".name"] ~= server then + if enable == 1 then + uci:set("ddns", s[".name"], "enabled", 0) + end + else + uci:set("ddns", s[".name"], "enabled", enable) + end + end + ) + uci:commit("ddns") + if enable == 1 then + return _ddnsRestart() + else + return true + end +end + +function ddnsInfo() + local LuciJson = require("cjson") + local LuciUtil = require("luci.util") + local result = { + ["on"] = 0, + ["list"] = {} + } + local status = LuciUtil.exec("/usr/sbin/ddnsd status") + if not XQFunction.isStrNil(status) then + status = LuciJson.decode(status) + if status.daemon == "on" then + result.on = 1 + end + for key, value in pairs(status) do + if key ~= "deamon" then + local id = _serverId(key) + if id then + value.enabled = tonumber(value.enabled) + value.id = id + value.servicename = SERVICES[key].service_name + table.insert(result.list, value) + end + end + end + end + return result +end + +function ddnsSwitch(on) + if on then + os.execute("/usr/sbin/ddnsd start") + else + os.execute("/usr/sbin/ddnsd stop") + end +end + +function getDdns(id) + if not tonumber(id) then + return false + end + local uci = require("luci.model.uci").cursor() + local server = MAP[tonumber(id)] + local result = {} + local ddns = uci:get_all("ddns", server) + if ddns then + result["username"] = ddns.username or "" + result["password"] = ddns.password or "" + result["forceinterval"] = tonumber(ddns.force_interval) or 0 + result["checkinterval"] = tonumber(ddns.check_interval) or 0 + result["domain"] = ddns.domain or "" + result["enabled"] = tonumber(ddns.enabled) or 0 + return result + end + return false +end + +function setDdns(id, enable, username, password, checkinterval, forceinterval, domain) + if not tonumber(id) then + return false + end + local server = MAP[tonumber(id)] + if XQFunction.isStrNil(username) + or XQFunction.isStrNil(password) + or XQFunction.isStrNil(domain) + or XQFunction.isStrNil(server) then + return false + end + checkinterval = tonumber(checkinterval) + forceinterval = tonumber(forceinterval) + if not checkinterval or not forceinterval then + return false + end + local denable = enable == 1 and 1 or 0 + if _saveConfig(server, denable, username, password, checkinterval, forceinterval, domain) then + return _ddnsRestart() + end + return false +end + +function editDdns(id, enable, username, password, checkinterval, forceinterval, domain) + if not tonumber(id) then + return false + end + local uci = require("luci.model.uci").cursor() + local server = MAP[tonumber(id)] + local ddns = uci:get_all("ddns", server) + if ddns then + if not XQFunction.isStrNil(username) and username ~= ddns.username then + uci:set("ddns", server, "username", username) + end + if not XQFunction.isStrNil(password) and password ~= ddns.password then + uci:set("ddns", server, "password", password) + end + if not XQFunction.isStrNil(domain) and domain ~= ddns.domain then + uci:set("ddns", server, "domain", domain) + end + if tonumber(checkinterval) and tonumber(checkinterval) ~= tonumber(ddns.check_interval) then + uci:set("ddns", server, "checkinterval", checkinterval) + end + if tonumber(forceinterval) and tonumber(forceinterval) ~= tonumber(ddns.force_interval) then + uci:set("ddns", server, "forceinterval", forceinterval) + end + local enabled = enable == 1 and 1 or 0 + if enabled ~= tonumber(ddns.enabled) then + uci:set("ddns", server, "enabled", enabled) + end + uci:commit("ddns") + if enabled ~= 0 or ddns.enabled ~= 0 then + _ddnsRestart() + end + return true + end + return false +end + +function deleteDdns(id) + if not tonumber(id) then + return false + end + local server = MAP[tonumber(id)] + if XQFunction.isStrNil(server) then + return false + end + local uci = require("luci.model.uci").cursor() + uci:delete("ddns", server) + uci:commit("ddns") + return true +end + +--- id (1/2/3...) +--- on (true/false) +function ddnsServerSwitch(id, on) + if id then + return _ddnsServerSwitch(MAP[id], on and 1 or 0) + end + return false +end + +function reload() + return _ddnsRestart() +end \ No newline at end of file diff --git a/Mi_Lua/xiaoqiang/module/XQDMZModule.lua b/Mi_Lua/xiaoqiang/module/XQDMZModule.lua new file mode 100644 index 0000000..0fef1b5 --- /dev/null +++ b/Mi_Lua/xiaoqiang/module/XQDMZModule.lua @@ -0,0 +1,275 @@ +module ("xiaoqiang.module.XQDMZModule", package.seeall) + +local XQFunction = require("xiaoqiang.common.XQFunction") +local XQConfigs = require("xiaoqiang.common.XQConfigs") + +DMZ_NVRAM = { + ["vlan1hwname"] = "et0", + ["vlan2hwname"] = "et0", + ["vlan3hwname"] = "et0", + ["vlan1ports"] = "2 5*", + ["vlan2ports"] = "4 5", + ["vlan3ports"] = "0 5*" +} + +DMZ_NETWORK_CONFIGS = { + -- switch_vlan + ["eth0_1"] = { + ["device"] = "eth0", + ["vlan"] = 1, + ["ports"] = "2 5*" + }, + ["eth0_3"] = { + ["device"] = "eth0", + ["vlan"] = 3, + ["ports"] = "0 5*" + }, + -- interface + ["dmz"] = { + ["ifname"] = "eth0.3", + ["proto"] = "static", + ["ipaddr"] = "", + ["netmask"] = "255.255.255.0" + } +} + +DMZ_FIREWALL_CONFIGS = { + -- zone + ["zonedmz"] = { + ["name"] = "dmz", + ["network"] = "dmz", + ["input"] = "REJECT", + ["output"] = "ACCEPT", + ["forward"] = "REJECT" + }, + -- rule + ["dmzdns"] = { + ["src"] = "dmz", + ["proto"] = "tcpudp", + ["dest_port"] = 53, + ["target"] = "ACCEPT" + }, + ["dmzdhcp"] = { + ["src"] = "dmz", + ["proto"] = "udp", + ["dest_port"] = 67, + ["target"] = "ACCEPT" + }, + -- forwarding + ["dmztowan"] = { + ["src"] = "dmz", + ["dest"] = "wan" + }, + ["lantodmz"] = { + ["src"] = "lan", + ["dest"] = "dmz" + }, + -- redirect + ["dmz"] = { + ["src"] = "wan", + ["proto"] = "all", + ["target"] = "DNAT", + ["dest"] = "lan", + ["dest_ip"] = "" + } +} + +DMZ_DHCP_CONFIGS = { + -- dhcp + ["dmz"] = { + ["interface"] = "dmz", + ["start"] = 100, + ["limit"] = 150, + ["leasetime"] = "12h", + ["force"] = 1 + } +} + +-- +-- Event +-- +function hookLanIPChangeEvent(ip) + if XQFunction.isStrNil(ip) then + return + end + local uci = require("luci.model.uci").cursor() + local lan = ip:gsub(".%d+$","") + local destip = uci:get("firewall", "dmz","dest_ip") + if not XQFunction.isStrNil(destip) then + destip = lan.."."..destip:match(".(%d+)$") + uci:set("firewall", "dmz", "dest_ip", destip) + uci:commit("firewall") + end +end + +function unsetDMZ(mode) + local uci = require("luci.model.uci").cursor() + if mode == 1 then + uci:delete("firewall", "zonedmz") + uci:delete("firewall", "dmzdns") + uci:delete("firewall", "dmzdhcp") + uci:delete("firewall", "dmztowan") + uci:delete("firewall", "lantodmz") + uci:delete("firewall", "dmz") + uci:commit("firewall") + uci:delete("dhcp", "dmz") + uci:commit("dhcp") + uci:delete("network", "dmz") + uci:delete("network", "eth0_3") + uci:commit("network") + XQFunction.nvramSet("vlan3hwname", nil) + XQFunction.nvramSet("vlan3ports", nil) + XQFunction.nvramSet("vlan2ports", "4 5") + XQFunction.nvramSet("vlan1ports", "0 2 5*") + XQFunction.setNetMode(nil) + XQFunction.nvramCommit() + elseif mode == 0 then + uci:delete("firewall", "dmz") + uci:commit("firewall") + XQFunction.setNetMode(nil) + XQFunction.nvramCommit() + end +end + +function _setSimpleDMZ(destip, destmac) + local uci = require("luci.model.uci").cursor() + local config = DMZ_FIREWALL_CONFIGS["dmz"] + local lanip = uci:get("network", "lan", "ipaddr") + local lanpre = lanip:gsub(".%d+$", "") + local destpre = destip:gsub(".%d+$", "") + if lanpre ~= destpre or lanip == destip then + return 2 + end + config.dest_ip = destip + uci:section("firewall", "redirect", "dmz", config) + uci:commit("firewall") + if not XQFunction.isStrNil(destmac) then + local XQLanWanUtil = require("xiaoqiang.util.XQLanWanUtil") + local bind = XQLanWanUtil.addBind(destmac, destip) + if bind == 0 then + XQLanWanUtil.saveBindInfo() + else + return bind + end + end + return 0 +end + +function _setComplexDMZ(destip, destmac) + local uci = require("luci.model.uci").cursor() + local LuciUtil = require("luci.util") + local lanip = uci:get("network", "lan", "ipaddr") + local ipv = LuciUtil.split(destip, ".") + ipv[4] = 1 + ipv = table.concat(ipv, ".") + local lanpre = lanip:gsub(".%d+$", "") + local destpre = destip:gsub(".%d+$", "") + if lanpre == destpre or lanip == destip then + return 2 + end + -- nvram + for key, value in pairs(DMZ_NVRAM) do + XQFunction.nvramSet(key, value) + end + XQFunction.nvramCommit() + -- network config + local eth0_1 = DMZ_NETWORK_CONFIGS["eth0_1"] + local eth0_3 = DMZ_NETWORK_CONFIGS["eth0_3"] + local dmz = DMZ_NETWORK_CONFIGS["dmz"] + dmz.ipaddr = ipv + uci:section("network", "switch_vlan", "eth0_1", eth0_1) + uci:section("network", "switch_vlan", "eth0_3", eth0_3) + uci:section("network", "interface", "dmz", dmz) + uci:commit("network") + -- firewall config + local zonedmz = DMZ_FIREWALL_CONFIGS["zonedmz"] + local dmzdns = DMZ_FIREWALL_CONFIGS["dmzdns"] + local dmzdhcp = DMZ_FIREWALL_CONFIGS["dmzdhcp"] + local dmztowan = DMZ_FIREWALL_CONFIGS["dmztowan"] + local lantodmz = DMZ_FIREWALL_CONFIGS["lantodmz"] + local fdmz = DMZ_FIREWALL_CONFIGS["dmz"] + fdmz.dest_ip = destip + uci:section("firewall", "zone", "zonedmz", zonedmz) + uci:section("firewall", "rule", "dmzdns", dmzdns) + uci:section("firewall", "rule", "dmzdhcp", dmzdhcp) + uci:section("firewall", "forwarding", "dmztowan", dmztowan) + uci:section("firewall", "forwarding", "lantodmz", lantodmz) + uci:section("firewall", "redirect", "dmz", fdmz) + uci:commit("firewall") + -- dhcp config + local dhcp = DMZ_DHCP_CONFIGS["dmz"] + uci:section("dhcp", "dhcp", "dmz", dhcp) + -- ip mac bind + if not XQFunction.isStrNil(destmac) then + local XQLanWanUtil = require("xiaoqiang.util.XQLanWanUtil") + local bind = XQLanWanUtil.addBind(destmac, destip) + if bind == 0 then + XQLanWanUtil.saveBindInfo() + else + return bind + end + end + return 0 +end + +function moduleOn() + local uci = require("luci.model.uci").cursor() + local dmzip = uci:get("firewall", "dmz", "dest_ip") + if dmzip then + return true + else + return false + end +end + +-- status +-- 0:关闭 +-- 1:开启 +-- 2:冲突(端口转发被开启,DMZ功能就不能开启) +function getDMZInfo() + local XQPortForward = require("xiaoqiang.module.XQPortForward") + local uci = require("luci.model.uci").cursor() + local info = {} + if XQPortForward.moduleOn() then + info["status"] = 2 + else + info["status"] = moduleOn() and 1 or 0 + if info.status == 1 then + info["ip"] = uci:get("firewall", "dmz", "dest_ip") or "" + end + end + info["lanip"] = uci:get("network", "lan", "ipaddr") or "" + return info +end + +-- mode 0/1 简单/复杂 +-- return 0/1/2/3/4 设置成功/IP冲突/MACIP不合法/工作模式不可设置/开启了端口转发,DMZ不能启用 +function setDMZ(mode, destip, destmac) + if XQFunction.isStrNil(destip) then + return 2 + end + local XQPortForward = require("xiaoqiang.module.XQPortForward") + if XQPortForward.moduleOn() then + return 4 + end + if mode == 0 then + XQFunction.setNetMode("dmzsimple") + return _setSimpleDMZ(destip, destmac) + elseif mode == 1 then + XQFunction.setNetMode("dmzmode") + return _setComplexDMZ(destip, destmac) + else + return 3 + end +end + +function dmzReload(mode) + if mode == 0 then + -- reload services + os.execute("/etc/init.d/firewall restart") + XQFunction.forkRestartDnsmasq() + elseif mode == 1 then + -- fork reboot + XQFunction.forkReboot() + end +end \ No newline at end of file diff --git a/Mi_Lua/xiaoqiang/module/XQGuestWifi.lua b/Mi_Lua/xiaoqiang/module/XQGuestWifi.lua new file mode 100644 index 0000000..968ddc4 --- /dev/null +++ b/Mi_Lua/xiaoqiang/module/XQGuestWifi.lua @@ -0,0 +1,197 @@ +module ("xiaoqiang.module.XQGuestWifi", package.seeall) + +local bit = require("bit") +local XQFunction = require("xiaoqiang.common.XQFunction") +local XQConfigs = require("xiaoqiang.common.XQConfigs") +--[[ +-- Config network +--- config switch_vlan 'eth0_3' +local SWITCH_VLAN = { + ["device"] = "eth0", + ["vlan"] = "3", + ["ports"] = "5", + ["xqmodule"] = "guest_wifi" +} + +--- config interface 'guest' +local NETWORK_GUEST = { + ["type"] = "bridge", + ["ifname"] = "eth0.3", + ["proto"] = "static", + ["ipaddr"] = "", + ["netmask"] = "255.255.255.0", + ["xqmodule"] = "guest_wifi" +} + +-- Config dhcp +--- config dhcp 'guest' +local DHCP_GUEST = { + ["interface"] = "guest", + ["leasetime"] = "12h", + ["dhcp_option_force"] = { + "43,XIAOMI_ROUTER" + }, + ["force"] = "1", + ["start"] = "100", + ["limit"] = "50", + ["xqmodule"] = "guest_wifi" +} + +-- Config firewall +local GUEST_ZONE = { + ["name"] = "guest", + ["network"] = "guest", + ["input"] = "REJECT", + ["forward"] = "REJECT", + ["output"] = "ACCEPT", + ["xqmodule"] = "guest_wifi" +} + +local FORWARDING_GUEST = { + ["src"] = "guest", + ["dest"] = "wan", + ["xqmodule"] = "guest_wifi" +} + +local RULE_GUEST1 = { + ["name"] = "Allow Guest DNS Queries", + ["src"] = "guest", + ["dest_port"] = "53", + ["proto"] = "tcpudp", + ["target"] = "ACCEPT", + ["xqmodule"] = "guest_wifi" +} + +local RULE_GUEST2 = { + ["name"] = "Allow Guest DHCP request", + ["src"] = "guest", + ["src_port"] = "67-68", + ["dest_port"] = "67-68", + ["proto"] = "udp", + ["target"] = "ACCEPT", + ["xqmodule"] = "guest_wifi" +} + +function _setnetwork() + local uci = require("luci.model.uci").cursor() + local XQLanWanUtil = require("xiaoqiang.util.XQLanWanUtil") + local guest = uci:get_all("network", "eth0_3") + if not guest then + local lanip = XQLanWanUtil.getLanIp() + local lan = lanip:gsub(".%d+.%d+$", "") + local cip = tonumber(lanip:match(".(%d+).%d+$")) + if cip > 150 then + cip = cip - 1 + else + cip = cip + 1 + end + NETWORK_GUEST.ipaddr = lan.."."..tostring(cip)..".1" + uci:section("network", "switch_vlan", "eth0_3", SWITCH_VLAN) + uci:section("network", "interface", "guest", NETWORK_GUEST) + uci:commit("network") + return 1 + end + return 0 +end + +function _delnetwork() + local uci = require("luci.model.uci").cursor() + uci:delete("network", "eth0_3") + uci:delete("network", "guest") + uci:commit("network") +end + +function _setdhcp() + local uci = require("luci.model.uci").cursor() + local guest = uci:get_all("dhcp", "guest") + if not guest then + uci:section("dhcp", "dhcp", "guest", DHCP_GUEST) + uci:commit("dhcp") + return 1 + end + return 0 +end + +function _deldhcp() + local uci = require("luci.model.uci").cursor() + uci:delete("dhcp", "guest") + uci:commit("dhcp") +end + +function _setfirewall() + local uci = require("luci.model.uci").cursor() + local guest = uci:get_all("firewall", "guest") + if not guest then + local sectionid = uci:add("firewall", "zone") + uci:section("firewall", "zone", sectionid, GUEST_ZONE) + uci:section("firewall", "forwarding", "guest", FORWARDING_GUEST) + local ruleid1 = uci:add("firewall", "rule") + uci:section("firewall", "rule", ruleid1, RULE_GUEST1) + local ruleid2 = uci:add("firewall", "rule") + uci:section("firewall", "rule", ruleid2, RULE_GUEST2) + uci:commit("firewall") + return 1 + end + return 0 +end + +function _delfirewall() + local uci = require("luci.model.uci").cursor() + uci:delete("firewall", "guest") + uci:foreach("firewall", "zone", + function(s) + if s["xqmodule"] == "guest_wifi" then + uci:delete("firewall", s[".name"]) + end + end + ) + uci:foreach("firewall", "rule", + function(s) + if s["xqmodule"] == "guest_wifi" then + uci:delete("firewall", s[".name"]) + end + end + ) + uci:commit("firewall") +end +]]-- + +function _checkGuestWifi() + local uci = require("luci.model.uci").cursor() + local guest = uci:get_all("network", "eth0_3") + if guest then + return true + else + return false + end +end + +function setGuestWifi(wifiIndex, ssid, encryption, key, enabled, open) + local XQLanWanUtil = require("xiaoqiang.util.XQLanWanUtil") + local XQWifiUtil = require("xiaoqiang.util.XQWifiUtil") + local success = XQWifiUtil.setGuestWifi(wifiIndex, ssid, encryption, key, enabled, open) + if not success then + return false + end + local networkrestart = true + if _checkGuestWifi() then + networkrestart = false + end + if networkrestart then + local lanip = XQLanWanUtil.getLanIp() + local lan = lanip:gsub(".%d+.%d+$", "") + local cip = tonumber(lanip:match(".(%d+).%d+$")) + cip = bit.bor(bit.band(1, cip + 1), bit.lshift(bit.rshift(cip, 1), 1)) + lanip = lan.."."..tostring(cip)..".1" + XQFunction.forkExec("sleep 4; /usr/sbin/guest_ssid_network start "..lanip.." 255.255.255.0 >/dev/null 2>/dev/null") + else + XQFunction.forkRestartWifi() + end + return true +end + +function delGuestWifi(wifiIndex) + local XQWifiUtil = require("xiaoqiang.util.XQWifiUtil") + XQWifiUtil.delGuestWifi(wifiIndex) + XQFunction.forkExec("sleep 4; /usr/sbin/guest_ssid_network stop >/dev/null 2>/dev/null") +end \ No newline at end of file diff --git a/Mi_Lua/xiaoqiang/module/XQNetworkSpeedTest.lua b/Mi_Lua/xiaoqiang/module/XQNetworkSpeedTest.lua new file mode 100644 index 0000000..0b7b020 --- /dev/null +++ b/Mi_Lua/xiaoqiang/module/XQNetworkSpeedTest.lua @@ -0,0 +1,94 @@ +module ("xiaoqiang.module.XQNetworkSpeedTest", package.seeall) + +local LuciFs = require("luci.fs") +local LuciSys = require("luci.sys") +local LuciUtil = require("luci.util") + +local XQFunction = require("xiaoqiang.common.XQFunction") +local XQConfigs = require("xiaoqiang.common.XQConfigs") + +local DIR = "/tmp/" +-- Kbyte +local POST_FILESIZE = 512 +-- Number of requests to perform +local REQUEST_TIMES = 40 +-- Number of multiple requests to make at a time +local REQUEST_NUM = 4 + +local TIMELIMITE = 5 +local TIMESTEP = 1 +local AB_CMD = "/usr/bin/ab" +local DD_CMD = "/bin/dd" + +local POST_URL = "http://netsp.master.qq.com/cgi-bin/netspeed" + +function execl(command, times) + local io = require("io") + local pp = io.popen(command) + local line = "" + local data = {} + if times < 1 then + return nil + end + while true do + line = pp:read() + if not XQFunction.isStrNil(line) then + local speed = tonumber(line:match("tx:(%S+)")) + if speed > 0 then + table.insert(data, speed) + else + break + end + else + break + end + end + pp:close() + if #data > 2 then + return data[#data] + else + return execl(command, times - 1) + end +end + +function uploadSpeedTest() + local postfile = DIR..LuciSys.uniqueid(8)..".dat" + local pfcmd = string.format("%s if=/dev/zero of=%s bs=1k count=%d >/dev/null 2>&1", DD_CMD, postfile, POST_FILESIZE) + os.execute(pfcmd) + if postfile and LuciFs.access(postfile) then + local cmd = string.format("%s -N -s %d -M %d -n %d -c %d -T 'multipart/form-data' -p %s '%s'", + AB_CMD, TIMESTEP, TIMELIMITE, REQUEST_TIMES, REQUEST_NUM, postfile, POST_URL) + local speed = execl(cmd, 2) + if postfile and LuciFs.access(postfile) then + LuciFs.unlink(postfile) + end + if speed then + return speed + else + return nil + end + else + local XQLog = require("xiaoqiang.XQLog") + XQLog.log(6, "create postfile error") + return nil + end +end + +function downloadSpeedTest() + local result = {} + local cmd = "/usr/bin/speedtest" + for _, line in ipairs(LuciUtil.execl(cmd)) do + if not XQFunction.isStrNil(line) then + table.insert(result, tonumber(line:match("rx:(%S+)"))) + end + end + if #result > 0 then + local speed = 0 + for _, value in ipairs(result) do + speed = speed + tonumber(value) + end + return speed/#result + else + return nil + end +end diff --git a/Mi_Lua/xiaoqiang/module/XQPortForward.lua b/Mi_Lua/xiaoqiang/module/XQPortForward.lua new file mode 100644 index 0000000..199e2e0 --- /dev/null +++ b/Mi_Lua/xiaoqiang/module/XQPortForward.lua @@ -0,0 +1,266 @@ +module ("xiaoqiang.module.XQPortForward", package.seeall) + +local XQFunction = require("xiaoqiang.common.XQFunction") +local XQConfigs = require("xiaoqiang.common.XQConfigs") + +-- +-- Event +-- +function hookLanIPChangeEvent(ip) + if XQFunction.isStrNil(ip) then + return + end + local uci = require("luci.model.uci").cursor() + local lan = ip:gsub(".%d+$","") + uci:foreach("firewall", "redirect", + function(s) + local ftype = tonumber(s.ftype) + if ftype then + local destip = s.dest_ip + destip = lan.."."..destip:match(".(%d+)$") + uci:set("firewall", s[".name"], "dest_ip", destip) + end + end + ) + uci:commit("firewall") +end + +-- proto +-- 1: tcp +-- 2: udp +-- 3: tcp and udp +-- default: 1/tcp +function _protoHelper(proto) + if proto and type(proto) == "number" then + if proto == 1 then + return "tcp" + elseif proto == 2 then + return "udp" + elseif proto == 3 then + return "tcpudp" + else + return "tcp" + end + end + if proto and type("proto") == "string" then + if proto == "tcp" then + return 1 + elseif proto == "udp" then + return 2 + elseif proto == "tcpudp" then + return 3 + else + return 1 + end + end + return nil +end + +function _portCheck(port) + if port and type(port) == "number" and port > 0 then + return true + else + return false + end +end + +function _portRangeOverlap(port1, port2) + local LuciUtil = require("luci.util") + if port1 and port2 then + port1 = tostring(port1) + port2 = tostring(port2) + if port1 == port2 then + return false + end + local range1 = {} + local range2 = {} + if port1:match("-") then + local sp = LuciUtil.split(port1, "-") + range1["f"] = tonumber(sp[1]) + range1["t"] = tonumber(sp[2]) + else + range1["f"] = tonumber(port1) + range1["t"] = tonumber(port1) + end + if port2:match("-") then + local sp = LuciUtil.split(port2, "-") + range2["f"] = tonumber(sp[1]) + range2["t"] = tonumber(sp[2]) + else + range2["f"] = tonumber(port2) + range2["t"] = tonumber(port2) + end + if (range1.f >= range2.f and range1.f <= range2.t) or + (range1.t >= range2.f and range1.t <= range2.t) then + return true + end + end + return false +end + +function _portConflictCheck(port) + local uci = require("luci.model.uci").cursor() + local result = false + uci:foreach("firewall", "redirect", + function(s) + if _portRangeOverlap(port, s.src_dport) then + result = true + end + end + ) + return result +end + +function moduleOn() + return #portForwards(0) > 0 +end + +-- status +-- 0:关闭 +-- 1:开启 +-- 2:冲突(DMZ功能开启,NAT功能就不能开启) +function portForwardInfo() + local XQDMZModule = require("xiaoqiang.module.XQDMZModule") + local info = {} + if XQDMZModule.moduleOn() then + info["status"] = 2 + else + info["status"] = moduleOn() and 1 or 0 + end + return info +end + +-- ftype +-- 0: all +-- 1: port +-- 2: range +function portForwards(ftype) + local uci = require("luci.model.uci").cursor() + local portforwards = {} + local forwardtype = tonumber(ftype) or 0 + uci:foreach("firewall", "redirect", + function(s) + local ftype = tonumber(s.ftype) + if ftype and forwardtype == 0 or forwardtype == ftype then + local item = {} + item.name = s.name + item.destip = s.dest_ip + item.proto = _protoHelper(s.proto) or 1 + if ftype == 1 then + item.srcport = tonumber(s.src_dport) + item.destport = s.dest_port + item.ftype = 1 + elseif ftype == 2 then + item.ftype = 2 + local portrange = {} + local LuciUtil = require("luci.util") + local sp = LuciUtil.split(s.src_dport, "-") + item.srcport = { + ["f"] = tonumber(sp[1]), + ["t"] = tonumber(sp[2]) + } + end + table.insert(portforwards, item) + end + end + ) + return portforwards +end + +-- return +-- 0: 设置成功 +-- 1: 参数不合法 +-- 2: 端口冲突(设置的端口和已有端口有重叠) +-- 3: 功能冲突由于DMZ开启 +function setPortForward(name, ip, sport, dport, proto) + if not XQFunction.isStrNil(ip) and _portCheck(tonumber(sport)) and _portCheck(tonumber(dport)) then + local uci = require("luci.model.uci").cursor() + if portForwardInfo().status == 2 then + return 3 + end + if _portConflictCheck(sport) then + return 2 + end + local sname = string.format("wan%srdr", tostring(sport)) + local options = { + ["src"] = "wan", + ["src_dport"] = sport, + ["proto"] = _protoHelper(tonumber(proto)) or "tcp", + ["target"] = "DNAT", + ["dest"] = "lan", + ["dest_port"] = dport, + ["dest_ip"] = ip, + ["ftype"] = 1, + ["name"] = name or "" + } + uci:section("firewall", "redirect", sname, options) + uci:commit("firewall") + return 0 + end + return 1 +end + +-- return +-- 0: 设置成功 +-- 1: 参数不合法 +-- 2: 端口冲突(设置的端口和已有端口有重叠) +function setRangePortForward(name, ip, fport, tport, proto) + if not XQFunction.isStrNil(ip) and _portCheck(tonumber(fport)) and _portCheck(tonumber(tport)) then + if tonumber(fport) > tonumber(tport) then + return 1 + end + local uci = require("luci.model.uci").cursor() + local sport = tostring(fport).."-"..tostring(tport) + if portForwardInfo().status == 2 then + return 3 + end + if _portConflictCheck(sport) then + return 2 + end + local sname = string.format("wan%srdr", tostring(fport)) + local options = { + ["src"] = "wan", + ["src_dport"] = sport, + ["proto"] = _protoHelper(tonumber(proto)) or "tcp", + ["target"] = "DNAT", + ["dest"] = "lan", + ["dest_ip"] = ip, + ["ftype"] = 2, + ["name"] = name or "" + } + uci:section("firewall", "redirect", sname, options) + uci:commit("firewall") + return 0 + end + return 1 +end + +function deletePortForward(port) + if _portCheck(tonumber(port)) then + local uci = require("luci.model.uci").cursor() + local sname = string.format("wan%srdr", tostring(port)) + uci:delete("firewall", sname) + uci:commit("firewall") + return true + end + return false +end + +function deleteAllPortForward() + local uci = require("luci.model.uci").cursor() + uci:delete_all("firewall", "redirect", + function(s) + if s.ftype then + return true + else + return false + end + end + ) + uci:commit("firewall") + return true +end + +function restart() + os.execute("/etc/init.d/firewall restart") +end \ No newline at end of file diff --git a/Mi_Lua/xiaoqiang/module/XQPredownload.lua b/Mi_Lua/xiaoqiang/module/XQPredownload.lua new file mode 100644 index 0000000..4b89240 --- /dev/null +++ b/Mi_Lua/xiaoqiang/module/XQPredownload.lua @@ -0,0 +1,35 @@ +module ("xiaoqiang.module.XQPredownload", package.seeall) + +local XQFunction = require("xiaoqiang.common.XQFunction") +local XQConfigs = require("xiaoqiang.common.XQConfigs") + +function predownloadInfo() + local uci = require("luci.model.uci").cursor() + local info = {} + info["enable"] = os.execute("/etc/init.d/predownload-ota status") == 0 and 1 or 0 + info["priority"] = tonumber(uci:get("otapred", "settings", "priority")) or 0 + return info +end + +function reload() + os.execute("/etc/init.d/predownload-ota restart") +end + +function switch(on) + local uci = require("luci.model.uci").cursor() + if on then + return os.execute("/etc/init.d/predownload-ota start") == 0 + else + return os.execute("/etc/init.d/predownload-ota stop") == 0 + end +end + +function setPriority(priority) + local priority = tonumber(priority) or 0 + if priority == 1 then + uci:set("otapred", "settings", "priority", "1") + else + uci:set("otapred", "settings", "priority", "0") + end + uci:commit("otapred") +end \ No newline at end of file diff --git a/Mi_Lua/xiaoqiang/util/XQCacheUtil.lua b/Mi_Lua/xiaoqiang/util/XQCacheUtil.lua new file mode 100644 index 0000000..3e1e55b --- /dev/null +++ b/Mi_Lua/xiaoqiang/util/XQCacheUtil.lua @@ -0,0 +1,43 @@ +module ("xiaoqiang.util.XQCacheUtil", package.seeall) + +local XQFunction = require("xiaoqiang.common.XQFunction") +local XQConfigs = require("xiaoqiang.common.XQConfigs") + +local LuciUtil = require("luci.util") +local LuciSys = require("luci.sys") +local Nixio = require "nixio" +local NixioFs = require "nixio.fs" + +function saveCache(key, data, expire) + if XQFunction.isStrNil(key) or not data or not expire then + return false + end + local path = "/tmp/"..key + local info = {} + info.data = data + info.atime = LuciSys.uptime() + info.expire = tostring(expire) + local cache = Nixio.open(path, "w", 600) + cache:writeall(LuciUtil.get_bytecode(info)) + cache:close() + return true +end + +function getCache(key) + if XQFunction.isStrNil(key) then + return nil + end + local path = "/tmp/"..key + if not NixioFs.access(path) then + return nil + end + local blob = NixioFs.readfile(path) + local func = loadstring(blob) + setfenv(func, {}) + + local data = func() + if data.atime and data.expire and tonumber(data.expire) > 0 and data.atime + data.expire < LuciSys.uptime() then + return nil + end + return data.data +end \ No newline at end of file diff --git a/Mi_Lua/xiaoqiang/util/XQCameraUtil.lua b/Mi_Lua/xiaoqiang/util/XQCameraUtil.lua new file mode 100644 index 0000000..465f5ce --- /dev/null +++ b/Mi_Lua/xiaoqiang/util/XQCameraUtil.lua @@ -0,0 +1,358 @@ +module ("xiaoqiang.util.XQCameraUtil", package.seeall) + + +local LuciProtocol = require("luci.http.protocol") +local urlencode = LuciProtocol.urlencode +local XQLog = require("xiaoqiang.XQLog") + + +-- utils +local XQDeviceUtil = require("xiaoqiang.util.XQDeviceUtil") +local XQFunction = require("xiaoqiang.common.XQFunction") +local XQHttpUtil = require("xiaoqiang.util.XQHttpUtil") + +local JSON = require("json") +local LuciUtil = require("luci.util") +local LuciFs = require("luci.fs") + +-- dirs config +local DIR_NAME = "摄像机监控视频" + +local ANTS_HTTP_BASE_MAIN = "http://%s/sd/record/" +local ANTS_HTTP_BASE_SUB = "http://%s/sd/record_sub/" + +local ANTS_HTTP_BASE = ANTS_HTTP_BASE_MAIN + +local MODEL = XQFunction.nvramGet("model") + +local UCI = require("luci.model.uci") +local CONF = "record_camera" +local CUR_FILE = "/tmp/crontab_record_camera.lua.FILE" +local DEFAULT_CONF = {} +DEFAULT_CONF.enable = "yes" +DEFAULT_CONF.hd = "yes" +DEFAULT_CONF.days = 1 + +local CURSOR = UCI.cursor() +local BASE_PATH = CURSOR:get("misc", "hardware", "camera") + +function getModel() + return MODEL +end + +function chmod777() + local chmodcmd = "/bin/chmod 777 -R %s%s" + LuciUtil.exec(chmodcmd:format(BASE_PATH,DIR_NAME)) +end + +function getCurrentDisk() + local disk = XQFunction.thrift_tunnel_to_datacenter([[{"api":26}]]) + if disk and disk.code == 0 then + return math.floor(tonumber(disk.free) / (1024*1024) ) + else + return 0 + end +end + +function getUid(did) + _,_, uid = did:find("%w+-%w+-(%w+)-?%w*") + return uid +end + +function getConfig(did) + local uid = getUid(did) + if not LuciFs.access("/etc/config/record_camera") then + LuciFs.writefile("/etc/config/record_camera","") + end + local c = UCI:cursor() + local conf = c:get_all(CONF,uid) + if not conf then + return DEFAULT_CONF + else + conf.days = tonumber(conf.days) + return conf + end +end + +function setConfig(did,config) + local uid = getUid(did) + local c = UCI:cursor() + c:section(CONF,"camera",uid,config) + c:save(CONF) + c:commit(CONF) +end + +function getDay(days) + local d = tonumber(days)*24*60*60 + if d < 86400 then + d = 86400 + end + local cmd = "/bin/date \"+%Y-%m-%d %H:%M\" -D %s -d $(( $(/bin/date +%s) - " .. d .. " )) " + local theday = LuciUtil.exec(cmd) + local r = {} + _,_,r.year,r.month,r.day,r.hour,r.min = theday:find("(%d+)-(%d+)-(%d+)%s+(%d+):(%d+)") + r.sec = "00" + return r +end + +function getCurrentPID() + local stat = LuciFs.readfile("/proc/self/stat") + local pid = "" + if stat then + _,_,pid = stat:find("^(%d+)%s.+") + end + return pid +end + +function isRunning() + local pid = LuciFs.readfile("/tmp/crontab_record_camera.lua.PID") + if pid then + return LuciFs.access("/proc/".. pid ) + end + return false +end + +function writePID() + local pid = getCurrentPID() + if pid then + LuciFs.writefile("/tmp/crontab_record_camera.lua.PID",pid) + end +end + +function getAntsCams() + local res = {} + local device_list = XQDeviceUtil.getConnectDeviceList() + for key,value in pairs(device_list) do + if value.ptype == 6 then + table.insert(res,value) + end + end + return res +end + +function getFilesOnCam(item,afterDay) + local res = {} + local base = ANTS_HTTP_BASE:format(item.ip) + local r = XQHttpUtil.httpGetRequest(base) + if r.code == 200 then + local list = string.gmatch(r.res,"
v2 then + local r1 = XQHttpUtil.httpGetRequest(base .. w .. "/") + if r1.code == 200 then + local list1 = string.gmatch(r1.res,"[^\n]+") + for w1 in list1 do + local f = {} + _,_,f.year,f.month,f.day,f.hour = w:find("(%d+)Y(%d+)M(%d+)D(%d+)H") + _,_,f.min,f.sec,f.size = w1:find(" 0 then + for _,v2 in pairs(files) do + local f = {} + f.file = "/" .. v .. "/" .. v1 .. "/" .. v2 + _,_,f.id,f.year,f.month,f.day,f.hour,f.min,f.sec = f.file:find("/(%S+)/(%d+)年(%d+)月(%d+)日(%d+)时/(%d+)分(%d+)秒.mp4") + if LuciFs.access( BASE_PATH .. DIR_NAME .. f.file ) then + local stat = LuciFs.stat(BASE_PATH .. DIR_NAME .. f.file ) + f.size = stat.size + table.insert(res,f) + end + end + else + XQLog.log(2,"XQCameraUtil:empty dir:" .. "/" .. v .. "/" .. v1 ) + LuciFs.rmdir(BASE_PATH .. DIR_NAME .. "/" .. v .. "/" .. v1) + end + end + end + end + end + return res +end + +function hasFile(f,fileList) + if fileList ~= nil then + for k,f1 in pairs(fileList) do + if f.year == f1.year and f.month == f1.month and f.day == f1.day and f.hour == f1.hour and f.min == f1.min and f.sec == f1.sec then + if f.size == f1.size then + XQLog.log(7,"XQCameraUtil:already download: " .. f1.file) + return true + end + end + end + end + return false +end + +function mergeFiles(remotefiles,localfiles) + local res = {} + for k,f in pairs(remotefiles) do + if not hasFile(f, localfiles) then + table.insert(res, f) + end + end + return res +end + +function doDelete(localfiles,afterDay) + for _,v in pairs(localfiles) do + if v.year and v.month and v.day and v.hour then + local v1 = v.year .. v.month .. v.day .. v.hour + local v2 = afterDay.year .. afterDay.month .. afterDay.day .. afterDay.hour + if v1 < v2 then + XQLog.log(2,"XQCameraUtil:delete: " .. v.file) + LuciFs.unlink(BASE_PATH .. DIR_NAME .. v.file) + end + end + end +end + +function doDownload(f) + XQLog.log(2,"XQCameraUtil:downloading: " .. f.url) + local cmd = "/usr/bin/curl \"%s\" -o \"%s\" --create-dirs --connect-timeout 3 -sS" + local fname = "%s%s/%s/%s年%s月%s日%s时/%s分%s秒.mp4" + local file = fname:format(BASE_PATH,DIR_NAME,f.id,f.year,f.month,f.day,f.hour,f.min,f.sec); + + LuciUtil.exec(cmd:format(f.url,file)) + if LuciFs.access(file) then + local stat = LuciFs.stat(file) + if f.size ~= stat.size then + XQLog.log(2,"XQCameraUtil:download error: expect " .. f.size .. " ,got " .. stat.size ) + LuciFs.unlink(file) + else + local fname1 = "%s年%s月%s日%s时%s分" + LuciFs.writefile(CUR_FILE,fname1:format( f.year,f.month,f.day,f.hour,f.min ) ) + chmod777() + end + end + +end + +-- function DoExec(cmd) +-- local LuciUtil = require("luci.util") +-- XQLog.log(7,cmd) +-- local s = LuciUtil.exec(cmd) + -- XQLog.log(7,s) +-- return s +-- end + +-- +-- camera backup api +-- zhangyanlu@xiaomi.com +-- + +local Errorm1 = "{ \"code\" : -1 , \"msg\" : \"api not exists\" }"; +local Error3 = "{ \"code\" : 3 , \"msg\" : \"parameter format error\" }"; +local OK0 = "{ \"code\" : 0 , \"msg\" : \"ok\" }"; + +function getAll(did) + if not did then + return Error3 + end + local res = {} + local cfg = getConfig(did) + res.config = cfg + if isRunning() then + res.isRunning = "yes" + else + res.isRunning = "no" + end + res.space = getCurrentDisk() + res.code = 0 + if LuciFs.access(CUR_FILE) then + res.file = LuciFs.readfile(CUR_FILE) + end + local p = "%s%s/%s/"; + res.path = p:format(BASE_PATH,DIR_NAME,getUid(did)) + return JSON.encode(res) +end + +function setAllConfig(did,cfg) + local _cfg = {} + if cfg.enable == "yes" then + _cfg.enable = "yes" + else + _cfg.enable = "no" + end + if cfg.days and tonumber(cfg.days) > 0 then + _cfg.days = cfg.days + else + _cfg.days = 1 + end + if cfg.hd == "yes" then + _cfg.hd = "yes" + else + _cfg.hd = "no" + end + if did and did ~= "" then + setConfig(did,_cfg) + end + return OK0 +end + +function runNow() + XQFunction.forkExec("/usr/sbin/crontab_record_camera.lua") + return OK0 +end + +function request(payload) + if payload == nil then + return Error3 + end + + local params = JSON.decode(payload) + if params == nil then + return Error3 + end + if params.command == "get_all" then + return getAll(params.did) + elseif params.command == "set_config" then + return setAllConfig(params.did,params.config) + elseif params.command == "run_backup" then + return runNow() + else + return Errorm1 + end + +end + + + + + diff --git a/Mi_Lua/xiaoqiang/util/XQCryptoUtil.lua b/Mi_Lua/xiaoqiang/util/XQCryptoUtil.lua new file mode 100644 index 0000000..3578c6c --- /dev/null +++ b/Mi_Lua/xiaoqiang/util/XQCryptoUtil.lua @@ -0,0 +1,81 @@ +module ("xiaoqiang.util.XQCryptoUtil", package.seeall) + +local b = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/' + +--base64 encoding +function binaryBase64Enc(data) + return ((data:gsub('.', function(x) + local r,b='',x:byte() + for i=8,1,-1 do r=r..(b%2^i-b%2^(i-1)>0 and '1' or '0') end + return r; + end)..'0000'):gsub('%d%d%d?%d?%d?%d?', function(x) + if (#x < 6) then return '' end + local c=0 + for i=1,6 do c=c+(x:sub(i,i)=='1' and 2^(6-i) or 0) end + return b:sub(c+1,c+1) + end)..({ '', '==', '=' })[#data%3+1]) +end + +--base64 decoding +function binaryBase64Dec(data) + data = string.gsub(data, '[^'..b..'=]', '') + return (data:gsub('.', function(x) + if (x == '=') then return '' end + local r,f='',(b:find(x)-1) + for i=6,1,-1 do r=r..(f%2^i-f%2^(i-1)>0 and '1' or '0') end + return r; + end):gsub('%d%d%d?%d?%d?%d?%d?%d?', function(x) + if (#x ~= 8) then return '' end + local c=0 + for i=1,8 do c=c+(x:sub(i,i)=='1' and 2^(8-i) or 0) end + return string.char(c) + end)) +end + +function md5File(file) + local LuciUtil = require("luci.util") + return LuciUtil.trim(LuciUtil.exec("/usr/bin/md5sum %s|/usr/bin/cut -d' ' -f1" % file)) +end + +function md5Str(str) + local LuciUtil = require("luci.util") + return LuciUtil.trim(LuciUtil.exec("/bin/echo -n %s|/usr/bin/md5sum|/usr/bin/cut -d' ' -f1" % str)) +end + +function sha256Str(str) + local LuciUtil = require("luci.util") + return LuciUtil.trim(LuciUtil.exec("/bin/echo -n %s|/usr/bin/sha256sum|/usr/bin/cut -d' ' -f1" % str)) +end + +function md5Base64Str(str) + local Mime = require("mime") + return md5Str(Mime.b64(str)) +end + +-- returns a hex string +function sha1(message) + local SHA1 = require("sha1") + return SHA1.sha1(message) +end + +-- returns raw bytes +function sha1Binary(message) + local SHA1 = require("sha1") + return SHA1.sha1_binary(message) +end + +function hash4SHA1(str) + return binaryBase64Enc(sha1Binary(str)) +end + +function binToHex(s) + return (s:gsub('(.)', function(c) + return string.format('%02x', string.byte(c)) + end)) +end + +function hextobin(s) + return (s:gsub('(%x%x)', function(hex) + return string.char(tonumber(hex, 16)) + end)) +end diff --git a/Mi_Lua/xiaoqiang/util/XQDBUtil.lua b/Mi_Lua/xiaoqiang/util/XQDBUtil.lua new file mode 100644 index 0000000..f9a2831 --- /dev/null +++ b/Mi_Lua/xiaoqiang/util/XQDBUtil.lua @@ -0,0 +1,219 @@ +module ("xiaoqiang.util.XQDBUtil", package.seeall) + +local SQLite3 = require("lsqlite3") +local XQ_DB = "/etc/xqDb" + +-- +-- |TABLE| USER_INFO(UUID,NAME,ICONURL) +-- |TABLE| PASSPORT_INFO(UUID,TOKEN,STOKEN,SID,SSECURITY) +-- |TABLE| DEVICE_INFO(MAC,ONAME,NICKNAME,COMPANY,OWNNERID) +-- + +function savePassport(uuid,token,stoken,sid,ssecurity) + local db = SQLite3.open(XQ_DB) + local fetch = string.format("select * from PASSPORT_INFO where UUID = '%s'",uuid) + local exist = false + for row in db:rows(fetch) do + if row then + exist = true + end + end + local sqlStr + if not exist then + sqlStr = string.format("insert into PASSPORT_INFO values('%s','%s','%s','%s','%s')",uuid,token,stoken,sid,ssecurity) + else + sqlStr = string.format("update PASSPORT_INFO set UUID = '%s', TOKEN = '%s', STOKEN = '%s', SID = '%s', SSECURITY = '%s' where UUID = '%s'",uuid,token,stoken,sid,ssecurity,uuid) + end + db:exec(sqlStr) + return db:close() +end + +function fetchPassport(uuid) + local db = SQLite3.open(XQ_DB) + local sqlStr = string.format("select * from PASSPORT_INFO where UUID = '%s'",uuid) + local result = {} + for row in db:rows(sqlStr) do + if row then + table.insert(result,{ + ["uuid"] = row[1], + ["token"] = row[2], + ["stoken"] = row[3], + ["sid"] = row[4], + ["ssecurity"] = row[5] + }) + end + end + db:close() + return result +end + +function fetchAllPassport() + local db = SQLite3.open(XQ_DB) + local sqlStr = "select * from PASSPORT_INFO" + local result = {} + for row in db:rows(sqlStr) do + if row then + table.insert(result,{ + ["uuid"] = row[1], + ["token"] = row[2], + ["stoken"] = row[3], + ["sid"] = row[4], + ["ssecurity"] = row[5] + }) + end + end + db:close() + return result +end + +function deletePassport(uuid) + local db = SQLite3.open(XQ_DB) + local sqlStr = string.format("delete from PASSPORT_INFO where UUID = '%s'",uuid) + db:exec(sqlStr) + return db:close() +end + +function saveUserInfo(uuid,name,iconUrl) + local db = SQLite3.open(XQ_DB) + local fetch = string.format("select * from USER_INFO where UUID = '%s'",uuid) + local exist = false + for row in db:rows(fetch) do + if row then + exist = true + end + end + local sqlStr + if not exist then + sqlStr = string.format("insert into USER_INFO values('%s','%s','%s')",uuid,name,iconUrl) + else + sqlStr = string.format("update USER_INFO set UUID = '%s', NAME = '%s', ICONURL = '%s' where UUID = '%s'",uuid,name,iconUrl,uuid) + end + db:exec(sqlStr) + return db:close() +end + +function fetchUserInfo(uuid) + local db = SQLite3.open(XQ_DB) + local sqlStr = string.format("select * from USER_INFO where UUID = '%s'",uuid) + local result = {} + for row in db:rows(sqlStr) do + if row then + table.insert(result,{ + ["uuid"] = row[1], + ["name"] = row[2], + ["iconUrl"] = row[3] + }) + end + end + db:close() + return result +end + +function fetchAllUserInfo() + local db = SQLite3.open(XQ_DB) + local sqlStr = string.format("select * from USER_INFO") + local result = {} + for row in db:rows(sqlStr) do + if row then + table.insert(result,{ + ["uuid"] = row[1], + ["name"] = row[2], + ["iconUrl"] = row[3] + }) + end + end + db:close() + return result +end + +function deleteUserInfo(uuid) + local db = SQLite3.open(XQ_DB) + local sqlStr = string.format("delete from USER_INFO where UUID = '%s'",uuid) + db:exec(sqlStr) + return db:close() +end + +function saveDeviceInfo(mac,oName,nickname,company,ownnerId) + local db = SQLite3.open(XQ_DB) + local fetch = string.format("select * from DEVICE_INFO where MAC = '%s'",mac) + local exist = false + for row in db:rows(fetch) do + if row then + exist = true + end + end + local sqlStr + if not exist then + sqlStr = string.format("insert into DEVICE_INFO values('%s','%s','%s','%s','%s')",mac,oName,nickname,company,ownnerId) + else + sqlStr = string.format("update DEVICE_INFO set MAC = '%s', ONAME = '%s', NICKNAME = '%s', COMPANY = '%s', OWNNERID = '%s' where MAC = '%s'",mac,oName,nickname,company,ownnerId,mac) + end + db:exec(sqlStr) + return db:close() +end + +function updateDeviceNickname(mac,nickname) + local db = SQLite3.open(XQ_DB) + local sqlStr = string.format("update DEVICE_INFO set NICKNAME = '%s' where MAC = '%s'",nickname,mac) + db:exec(sqlStr) + return db:close() +end + +function updateDeviceOwnnerId(mac,ownnerId) + local db = SQLite3.open(XQ_DB) + local sqlStr = string.format("update DEVICE_INFO set OWNNERID = '%s' where MAC = '%s'",ownnerId,mac) + db:exec(sqlStr) + return db:close() +end + +function updateDeviceCompany(mac,company) + local db = SQLite3.open(XQ_DB) + local sqlStr = string.format("update DEVICE_INFO set COMPANY = '%s' where MAC = '%s'",company,mac) + db:exec(sqlStr) + return db:close() +end + +function fetchDeviceInfo(mac) + local db = SQLite3.open(XQ_DB) + local sqlStr = string.format("select * from DEVICE_INFO where MAC = '%s'",mac) + local result = {} + for row in db:rows(sqlStr) do + if row then + result = { + ["mac"] = row[1], + ["oName"] = row[2], + ["nickname"] = row[3], + ["company"] = row[4], + ["ownnerId"] = row[5] + } + end + end + db:close() + return result +end + +function fetchAllDeviceInfo() + local db = SQLite3.open(XQ_DB) + local sqlStr = string.format("select * from DEVICE_INFO") + local result = {} + for row in db:rows(sqlStr) do + if row then + table.insert(result,{ + ["mac"] = row[1], + ["oName"] = row[2], + ["nickname"] = row[3], + ["company"] = row[4], + ["ownnerId"] = row[5] + }) + end + end + db:close() + return result +end + +function deleteDeviceInfo(mac) + local db = SQLite3.open(XQ_DB) + local sqlStr = string.format("delete from DEVICE_INFO where MAC = '%s'",mac) + db:exec(sqlStr) + return db:close() +end diff --git a/Mi_Lua/xiaoqiang/util/XQDeviceUtil.lua b/Mi_Lua/xiaoqiang/util/XQDeviceUtil.lua new file mode 100644 index 0000000..2d52bcd --- /dev/null +++ b/Mi_Lua/xiaoqiang/util/XQDeviceUtil.lua @@ -0,0 +1,571 @@ +module ("xiaoqiang.util.XQDeviceUtil", package.seeall) + +local Json = require("cjson") + +local XQConfigs = require("xiaoqiang.common.XQConfigs") +local XQFunction = require("xiaoqiang.common.XQFunction") +local XQEquipment = require("xiaoqiang.XQEquipment") + +function getDeviceCompany(mac) + local companyInfo = { name = "", icon = "" } + if XQFunction.isStrNil(mac) or string.len(mac) < 8 then + return companyInfo + end + return XQEquipment.identifyDevice(mac, nil) +end + +function getDeviceInfoFromDB() + local XQDBUtil = require("xiaoqiang.util.XQDBUtil") + local result = {} + local deviceList = XQDBUtil.fetchAllDeviceInfo() + if #deviceList > 0 then + for _,device in ipairs(deviceList) do + result[device.mac] = device + end + end + return result +end + +function saveDeviceName(mac,name) + local XQDBUtil = require("xiaoqiang.util.XQDBUtil") + local code = XQDBUtil.updateDeviceNickname(XQFunction.macFormat(mac),name) + if code == 0 then + return true + else + return false + end +end + +-- +-- Get DHCP list +-- + +function getDHCPList() + local NixioFs = require("nixio.fs") + local LuciUci = require("luci.model.uci") + local uci = LuciUci.cursor() + local result = {} + local leasefile = XQConfigs.DHCP_LEASE_FILEPATH + uci:foreach("dhcp", "dnsmasq", + function(s) + if s.leasefile and NixioFs.access(s.leasefile) then + leasefile = s.leasefile + return false + end + end) + local dhcp = io.open(leasefile, "r") + if dhcp then + for line in dhcp:lines() do + if line then + local ts, mac, ip, name = line:match("^(%d+) (%S+) (%S+) (%S+)") + if name == "*" then + name = "" + end + if ts and mac and ip and name then + result[#result+1] = { + mac = XQFunction.macFormat(mac), + ip = ip, + name = name + } + end + end + end + dhcp:close() + return result + else + return false + end +end + +function getDHCPDict() + local dhcpDict = {} + local dhcpList = getDHCPList() + for _,value in ipairs(dhcpList) do + dhcpDict[value.mac] = value + end + return dhcpDict +end + +function getDHCPIpDict() + local dhcpDict = {} + local dhcpList = getDHCPList() + for _,value in ipairs(dhcpList) do + dhcpDict[value.ip] = value + end + return dhcpDict +end + +function getMacfilterInfoList() + local LuciUtil = require("luci.util") + local macFilterInfo = {} + local metaData = LuciUtil.execi("/usr/sbin/sysapi macfilter get") + for filterInfo in metaData do + filterInfo = filterInfo..";" + local mac = filterInfo:match('mac=(%S-);') or "" + local wan = filterInfo:match('wan=(%S-);') or "" + local lan = filterInfo:match('lan=(%S-);') or "" + local admin = filterInfo:match('admin=(%S-);') or "" + local pridisk = filterInfo:match('pridisk=(%S-);') or "" + local entry = {} + if (not XQFunction.isStrNil(mac)) then + entry["mac"] = XQFunction.macFormat(mac) + entry["wan"] = (string.upper(wan) == "YES" and true or false) + entry["lan"] = (string.upper(lan) == "YES" and true or false) + entry["admin"] = (string.upper(admin) == "YES" and true or false) + entry["pridisk"] = (string.upper(pridisk) == "YES" and true or false) + table.insert(macFilterInfo, entry) + end + end + return macFilterInfo +end + +function getMacfilterInfoDict() + local macFilterDict = {} + local macFilterList = getMacfilterInfoList() + for _,value in ipairs(macFilterList) do + macFilterDict[value.mac] = value + end + return macFilterDict +end + +--[[ +@param devName : lan/wan,其他情况 DEVNAME = DEV +]]-- +function getWanLanNetworkStatistics(devName) + local LuciUtil = require("luci.util") + local tracmd = "" + if devName == "lan" then + tracmd = "ubus call trafficd lan" + elseif devName == "wan" then + tracmd = "ubus call trafficd wan" + end + local statistics = { + ["upload"] = "0", + ["upspeed"] = "0", + ["download"] = "0", + ["downspeed"] = "0", + ["devname"] = "", + ["maxuploadspeed"] = "0", + ["maxdownloadspeed"] = "0" + } + + local ubusinfo = LuciUtil.exec(tracmd) + if XQFunction.isStrNil(ubusinfo) then + return statistics + end + local ubusinfo = Json.decode(ubusinfo) + if devName == "wan" then + statistics.devname = tostring(ubusinfo.ifname) + statistics.upload = tostring(ubusinfo.tx_bytes) + statistics.download = tostring(ubusinfo.rx_bytes) + statistics.upspeed = tostring(math.floor(ubusinfo.tx_rate or 0)) + statistics.downspeed = tostring(math.floor(ubusinfo.rx_rate or 0)) + statistics.maxuploadspeed = tostring(math.floor(ubusinfo.max_tx_rate or 0)) + statistics.maxdownloadspeed = tostring(math.floor(ubusinfo.max_rx_rate or 0)) + + local history = LuciUtil.exec("ubus call trafficd list_wan_rate") + if not XQFunction.isStrNil(history) then + historylist = {} + history = Json.decode(history) + for _, rate in ipairs(history.rate) do + if rate then + table.insert(historylist, tostring(rate)) + end + end + statistics.history = table.concat(historylist, ",") + end + else + statistics.devname = tostring(ubusinfo.ifname) + statistics.upload = tostring(ubusinfo.rx_bytes) + statistics.download = tostring(ubusinfo.tx_bytes) + statistics.upspeed = tostring(math.floor(ubusinfo.rx_rate or 0)) + statistics.downspeed = tostring(math.floor(ubusinfo.tx_rate or 0)) + statistics.maxuploadspeed = tostring(math.floor(ubusinfo.max_rx_rate or 0)) + statistics.maxdownloadspeed = tostring(math.floor(ubusinfo.max_tx_rate or 0)) + end + return statistics +end + +--[[ +@param mac=B8:70:F4:27:0C:1B 网卡mac地址 +@param upload=14745 主机当前累计上传数据总量(byte) +@param upspeed=54 主机5秒平均上传速度(byte/s) +@param download=25777 主机当前累计下载数据总量(byte) +@param downspeed=120 主机5秒平均下载速度(byte/s) +@param oneline=169 主机在线时长(秒) +@param devname 设备名 +@param maxuploadspeed 上传峰值 +@param maxdownloadspeed 下载峰值 +]]-- +function getDevNetStatisticsList() + local LuciUtil = require("luci.util") + local statList = {} + local dhcpNameDict = getDHCPDict() + local deviceInfoDict = getDeviceInfoFromDB() + + local deviceinfo = LuciUtil.exec("ubus call trafficd hw") + if XQFunction.isStrNil(deviceinfo) then + return statList + else + deviceinfo = Json.decode(deviceinfo) + end + for key, dev in pairs(deviceinfo) do + if dev then + local item = {} + local mac = XQFunction.macFormat(key) + local name, nickName, oriName + if dhcpNameDict[mac] then + oriName = dhcpNameDict[mac].name + end + local device = deviceInfoDict[mac] + if device then + if XQFunction.isStrNil(oriName) then + oriName = device.oName + end + nickName = device.nickname + end + local company = XQEquipment.identifyDevice(mac, oriName) + local dtype = company["type"] + if not XQFunction.isStrNil(nickName) then + name = nickName + end + if XQFunction.isStrNil(name) and not XQFunction.isStrNil(dtype.n) then + name = dtype.n + end + if XQFunction.isStrNil(name) and not XQFunction.isStrNil(oriName) then + name = oriName + end + if XQFunction.isStrNil(name) and not XQFunction.isStrNil(company.name) then + name = company.name + end + if XQFunction.isStrNil(name) then + name = mac + end + + local tx_bytes, tx_rate, rx_bytes, rx_rate, max_tx_rate, max_rx_rate = 0, 0, 0, 0, 0, 0 + local iplist = dev.ip_list + if #iplist > 0 then + for _, ip in ipairs(iplist) do + tx_bytes = tx_bytes + ip.tx_bytes or 0 + rx_bytes = rx_bytes + ip.rx_bytes or 0 + tx_rate = tx_rate + ip.tx_rate or 0 + rx_rate = rx_rate + ip.rx_rate or 0 + max_tx_rate = max_tx_rate + ip.max_tx_rate or 0 + max_rx_rate = max_rx_rate + ip.max_rx_rate or 0 + end + end + item["mac"] = mac + item["upload"] = tostring(tx_bytes) + item["upspeed"] = tostring(math.floor(tx_rate)) + item["download"] = tostring(rx_bytes) + item["downspeed"] = tostring(math.floor(rx_rate)) + item["online"] = tostring(dev.online_timer or 0) + item["devname"] = name + item["maxuploadspeed"] = tostring(math.floor(max_tx_rate)) + item["maxdownloadspeed"] = tostring(math.floor(max_rx_rate)) + statList[#statList+1] = item + end + end + return statList +end + +function getDevNetStatisticsDict() + local statDict = {} + local statlist = getDevNetStatisticsList() + for _, item in ipairs(statlist) do + if item then + statDict[item.mac] = item + end + end + return statDict +end + +function getConnectDeviceCount() + local LuciUtil = require("luci.util") + local count = 0 + local deviceinfo = LuciUtil.exec("ubus call trafficd hw") + if XQFunction.isStrNil(deviceinfo) then + return count + else + deviceinfo = Json.decode(deviceinfo) + end + for _,device in pairs(deviceinfo) do + if device and device.ip_list and #device.ip_list > 0 then + local dev = device.ifname + if not XQFunction.isStrNil(dev) and (tonumber(device.assoc) == 1 or not dev:match("wl")) then + local tcount = 0 + for _,ip in ipairs(device.ip_list) do + if ip.ageing_timer <= 300 then + if tcount >= 1 then + break + end + count = count + 1 + tcount = tcount + 1 + end + end + end + end + end + return count +end + +--[[ +@return online: 0 (offline) 1 (online) +@return ip: ip address +@return mac: mac address +@return type: wifi/line +@return tag: 1 (normal) 2 (in denylist) +@return port: 1 (2.4G wifi) 2 (5G wifi) +@return name: name for show +@return origin_name: origin name +@return signal: wifi signal +@return statistics: +]]-- +function getConnectDeviceList() + local XQWifiUtil = require("xiaoqiang.util.XQWifiUtil") + local XQDBUtil = require("xiaoqiang.util.XQDBUtil") + local XQEquipment = require("xiaoqiang.XQEquipment") + local XQLog = require("xiaoqiang.XQLog") + + local LuciUtil = require("luci.util") + local deviceList = {} + + local deviceinfo = LuciUtil.exec("ubus call trafficd hw") + if XQFunction.isStrNil(deviceinfo) then + return deviceList + else + deviceinfo = Json.decode(deviceinfo) + end + + local macFilterDict = getMacfilterInfoDict() + local dhcpDeviceDict = getDHCPDict() + local dhcpDevIpDict = getDHCPIpDict() + local deviceInfoDict = getDeviceInfoFromDB() + local wifiDeviceDict = XQWifiUtil.getAllWifiConnetDeviceDict() + + for key ,item in pairs(deviceinfo) do + if item and item.ip_list and #item.ip_list > 0 then + local devicesignal, devicetype, deviceport + local dev = item.ifname + local mac = XQFunction.macFormat(key) + + if not XQFunction.isStrNil(dev) and (tonumber(item.assoc) == 1 or not dev:match("wl")) then + -- 信号强度 + local signal = wifiDeviceDict[mac] + if signal and signal.signal then + devicesignal = signal.signal + else + devicesignal = "" + end + if dev:match("eth") then + devicetype = "line" + deviceport = 0 + elseif dev == "wl0" then + devicetype = "wifi" + deviceport = 2 + elseif dev == "wl1" then + devicetype = "wifi" + deviceport = 1 + elseif dev == "wl1.2" then + devicetype = "wifi" + deviceport = 3 + end + + -- 访问权限 + local authority = {} + if (macFilterDict[mac]) then + authority["wan"] = macFilterDict[mac]["wan"] and 1 or 0 + authority["lan"] = macFilterDict[mac]["lan"] and 1 or 0 + authority["admin"] = macFilterDict[mac]["admin"] and 1 or 0 + authority["pridisk"] = macFilterDict[mac]["pridisk"] and 1 or 0 + else + authority["wan"] = 1 + authority["lan"] = 1 + authority["admin"] = 1 + -- private disk deny access default + authority["pridisk"] = 0 + end + + local deviceInfo = deviceInfoDict[mac] + local dhcpinfo = dhcpDeviceDict[mac] + if not deviceInfo and dhcpinfo then + XQDBUtil.saveDeviceInfo(mac,dhcpinfo.name,"","","") + end + local count = 0 + for _,ip in ipairs(item.ip_list) do + if ip.ageing_timer <= 300 then + if count >= 1 then + break + end + -- 设备名称 + local name, originName, nickName, company + if dhcpDevIpDict[ip.ip] ~= nil then + originName = dhcpDevIpDict[ip.ip].name + end + + if originName and originName:match("^xiaomi%-ir") then -- fix miio model string + originName = originName:gsub("%-",".") + end + + if deviceInfo then + if XQFunction.isStrNil(originName) then + originName = deviceInfo.oName + end + nickName = deviceInfo.nickname + end + if not XQFunction.isStrNil(nickName) then + name = nickName + end + company = XQEquipment.identifyDevice(mac, originName) + local dtype = company["type"] + if XQFunction.isStrNil(name) and not XQFunction.isStrNil(dtype.n) then + name = dtype.n + end + if XQFunction.isStrNil(name) and not XQFunction.isStrNil(originName) then + name = originName + end + if XQFunction.isStrNil(name) and not XQFunction.isStrNil(company.name) then + name = company.name + end + if XQFunction.isStrNil(name) then + name = mac + end + if dtype.c == 3 and XQFunction.isStrNil(nickName) then + name = dtype.n + end + local device = { + ["ip"] = ip.ip, + ["mac"] = mac, + ["online"] = 1, + ["type"] = devicetype, + ["port"] = deviceport, + ["ctype"] = dtype.c, + ["ptype"] = dtype.p, + ["origin_name"] = originName or "", + ["name"] = name, + ["company"] = company, + ["authority"] = authority + } + local statistics = {} + statistics["dev"] = dev + statistics["mac"] = mac + statistics["ip"] = ip.ip + statistics["upload"] = tostring(ip.tx_bytes or 0) + statistics["upspeed"] = tostring(math.floor(ip.tx_rate or 0)) + statistics["download"] = tostring(ip.rx_bytes or 0) + statistics["downspeed"] = tostring(math.floor(ip.rx_rate or 0)) + statistics["online"] = tostring(item.online_timer or 0) + statistics["maxuploadspeed"] = tostring(math.floor(ip.max_tx_rate or 0)) + statistics["maxdownloadspeed"] = tostring(math.floor(ip.max_rx_rate or 0)) + device["statistics"] = statistics + table.insert(deviceList, device) + count = count + 1 + end + end + end + end + end + -- if #deviceList > 0 then + -- table.sort(deviceList, + -- function(a, b) + -- return b.statistics.onlinets < a.statistics.onlinets + -- end + -- ) + -- end + return deviceList +end + +function getConDevices(withbrlan) + local XQDBUtil = require("xiaoqiang.util.XQDBUtil") + local XQEquipment = require("xiaoqiang.XQEquipment") + local XQLog = require("xiaoqiang.XQLog") + + local LuciUtil = require("luci.util") + local deviceList = {} + + local deviceinfo = LuciUtil.exec("ubus call trafficd hw") + if XQFunction.isStrNil(deviceinfo) then + return deviceList + else + deviceinfo = Json.decode(deviceinfo) + end + + local dhcpDevIpDict = getDHCPIpDict() + --local dhcpDeviceDict = getDHCPDict() + local deviceInfoDict = getDeviceInfoFromDB() + + for key ,item in pairs(deviceinfo) do + if item and item.ip_list and #item.ip_list > 0 then + local dev = item.ifname + local mac = XQFunction.macFormat(key) + + if not XQFunction.isStrNil(dev) and ((not dev:match("wl") and withbrlan) or (dev:match("wl") and tonumber(item.assoc) == 1)) then + local count = 0 + for _,ip in ipairs(item.ip_list) do + if count >= 1 then + break + end + local name, originName, nickName, company + if dhcpDevIpDict[ip.ip] ~= nil then + originName = dhcpDevIpDict[ip.ip].name + end + local deviceInfo = deviceInfoDict[mac] + if deviceInfo then + if XQFunction.isStrNil(originName) then + originName = deviceInfo.oName + end + nickName = deviceInfo.nickname + end + if not XQFunction.isStrNil(nickName) then + name = nickName + end + company = XQEquipment.identifyDevice(mac, originName) + + local dtype = company["type"] + if XQFunction.isStrNil(name) and not XQFunction.isStrNil(dtype.n) then + name = dtype.n + end + if XQFunction.isStrNil(name) and not XQFunction.isStrNil(originName) then + name = originName + end + if XQFunction.isStrNil(name) and not XQFunction.isStrNil(company.name) then + name = company.name + end + if XQFunction.isStrNil(name) then + name = mac + end + if dtype.c == 3 and XQFunction.isStrNil(nickName) then + name = dtype.n + end + if ip.ageing_timer <= 300 then + local device = { + ["ip"] = ip.ip, + ["mac"] = mac, + ["online"] = 1, + ["ctype"] = dtype.c, + ["ptype"] = dtype.p, + ["origin_name"] = originName or "", + ["name"] = name, + ["company"] = company + } + local statistics = {} + statistics["dev"] = dev + statistics["mac"] = mac + statistics["ip"] = ip.ip + statistics["upload"] = tostring(ip.tx_bytes or 0) + statistics["upspeed"] = tostring(math.floor(ip.tx_rate or 0)) + statistics["download"] = tostring(ip.rx_bytes or 0) + statistics["downspeed"] = tostring(math.floor(ip.rx_rate or 0)) + statistics["online"] = tostring(item.online_timer or 0) + statistics["maxuploadspeed"] = tostring(math.floor(ip.max_tx_rate or 0)) + statistics["maxdownloadspeed"] = tostring(math.floor(ip.max_rx_rate or 0)) + device["statistics"] = statistics + table.insert(deviceList, device) + count = count + 1 + end + end + end + end + end + return deviceList +end diff --git a/Mi_Lua/xiaoqiang/util/XQDownloadUtil.lua b/Mi_Lua/xiaoqiang/util/XQDownloadUtil.lua new file mode 100644 index 0000000..be2a6fe --- /dev/null +++ b/Mi_Lua/xiaoqiang/util/XQDownloadUtil.lua @@ -0,0 +1,390 @@ +module ("xiaoqiang.util.XQDownloadUtil", package.seeall) + +local XQFunction = require("xiaoqiang.common.XQFunction") +local XQConfigs = require("xiaoqiang.common.XQConfigs") +local XQPreference = require("xiaoqiang.XQPreference") + +local LuciJson = require("json") +local LuciUtil = require("luci.util") +local XQLog = require("xiaoqiang.XQLog") +local XQCryptoUtil = require("xiaoqiang.util.XQCryptoUtil") + +local TMPFILEPATH = "/tmp/rom.bin" +local UDISKFILEPATH = "/userdisk/rom.bin" + +PREF_DOWNLOAD_TYPE = "DOWNLOAD_TYPE" +PREF_DOWNLOAD_FILE_PATH = "DOWNLOAD_FILE_PATH" + +local WGET_PIDS = [[ps w | grep wget | awk '{print $1}']] + +local WGET_FILESIZE_FILEDIR = "/tmp/" +local WGET_CHECK_FILESIZE = "wget -t3 -T10 --spider '%s' -o %s" +local GET_WGET_FILESIZE = "cat %s | grep Length | awk '{print $2}'" + +function wgetCheckDownloadFilesize(url) + if XQFunction.isStrNil(url) then + return nil + end + local LuciFs = require("luci.fs") + local LuciSys = require("luci.sys") + local random = LuciSys.uniqueid(8) + local filepath = WGET_FILESIZE_FILEDIR..random + local checkcmd = string.format(WGET_CHECK_FILESIZE, url, filepath) + if os.execute(checkcmd) then + local getcmd = string.format(GET_WGET_FILESIZE, filepath) + local size = tonumber(LuciUtil.exec(getcmd)) + LuciFs.unlink(filepath) + if size then + return size + end + end + LuciFs.unlink(filepath) + return nil +end + +function _checkResource(downloadUrl) + if XQFunction.isStrNil(downloadUrl) then + return false + end + local check = os.execute(XQConfigs.DOWNLOAD_RESOURCE_CHECK..downloadUrl) + if check ~= 0 then + return false + end + return true +end + +function _wgetDownload(downloadUrl) + if XQFunction.isStrNil(downloadUrl) then + return false + end + if not _checkResource(downloadUrl) then + XQLog.log(6, "Wget --spider : Bad url "..downloadUrl) + return false + end + XQPreference.set(PREF_DOWNLOAD_TYPE, 2) + local XQSysUtil = require("xiaoqiang.util.XQSysUtil") + local LuciFs = require("luci.fs") + local filePath + local filesize = XQPreference.get(XQConfigs.PREF_ROM_FULLSIZE, nil) + if filesize then + filesize = tonumber(filesize) + if XQSysUtil.checkDiskSpace(filesize) then + filePath = UDISKFILEPATH + elseif XQSysUtil.checkTmpSpace(filesize) then + filePath = TMPFILEPATH + else + return false + end + else + return false + end + XQPreference.set(PREF_DOWNLOAD_FILE_PATH, filePath) + if LuciFs.access(filePath) then + LuciFs.unlink(filePath) + end + local download = "wget -t3 -T30 '"..downloadUrl.."' -O "..filePath + LuciUtil.exec(download) + return XQCryptoUtil.md5File(filePath), filePath +end + +function _pauseDownload(ids) + if XQFunction.isStrNil(ids) then + XQPreference.set(XQConfigs.PREF_PAUSED_IDS, "") + return + end + XQPreference.set(XQConfigs.PREF_PAUSED_IDS, ids) + local payload = { + ["api"] = 505, + ["idList"] = ids + } + XQFunction.thrift_tunnel_to_datacenter(LuciJson.encode(payload)) +end + +function _resumeDownload(ids) + if XQFunction.isStrNil(ids) then + return + end + local payload = { + ["api"] = 506, + ["idList"] = ids + } + XQFunction.thrift_tunnel_to_datacenter(LuciJson.encode(payload)) +end + +function _deleteDownload(ids) + if XQFunction.isStrNil(ids) then + return + end + local payload = { + ["api"] = 507, + ["idList"] = ids, + ["deletefile"] = true + } + XQFunction.thrift_tunnel_to_datacenter(LuciJson.encode(payload)) +end + +--[[ + DownloadStatusNone, + Downloading = 1, + DownloadPause = 2, + DownloadCompleted = 4, + DownloadStoped = 8, + DownloadStatusFailed = 16, + DownloadNotStart = 32 +]]-- +function _xunleiDownload(downloadUrl, priority) + local priority = priority or 1 + XQPreference.set(PREF_DOWNLOAD_TYPE, 1) + local payload = { + ["api"] = 504, + ["url"] = downloadUrl, + ["type"] = 1, + ["redownload"] = 0, + ["hidden"] = true, + ["path"] = XQConfigs.USERDISK_DOWNLOAD_DIR, + ["dupId"] = "" + } + local ids = {} + local clist = {} + if priority == 1 then + local dolist = XQFunction.thrift_tunnel_to_datacenter([[{"api":503,"hidden":true}]]) + if dolist and dolist.code == 0 then + table.foreach(dolist.uncompletedList, + function(i,v) + if v.downloadStatus == 1 or v.downloadStatus == 32 then + table.insert(ids, v.id) + end + end + ) + clist = dolist.completedList + else + XQLog.log(6, "api 503 failed, will switch to wget") + return false, false, 0 + end + end + ids = table.concat(ids, ";") + _pauseDownload(ids) + + local download = XQFunction.thrift_tunnel_to_datacenter(LuciJson.encode(payload)) + if not download then + return false, false, 0 + end + if download and download.code ~= 2010 and download.code ~= 0 then + XQLog.log(6, "Xunlei download failed!") + return false, false, 0 + end + if download.code == 2010 then + local LuciFs = require("luci.fs") + local localFileName + for _, item in ipairs(clist) do + if item.id == download.info.id then + localFileName = item.localFileName + end + end + if not XQFunction.isStrNil(localFileName) and LuciFs.access(localFileName) then + download.code = 0 + XQLog.log(6, "File exist (predownload hit)") + else + XQLog.log(6, "Retry !!!") + payload.dupId = download.info.id + payload.redownload = 1 + download = XQFunction.thrift_tunnel_to_datacenter(LuciJson.encode(payload)) + end + end + local dId + if not download then + return false, false, 0 + end + if download and download.code ~= 0 then + return false, nil, 0 + else + dId = download.info.id + end + XQPreference.set(XQConfigs.PREF_ROM_DOWNLOAD_ID, dId) + local nodata = 0 + local nomatch = 0 + local lastsize = 0 + while true do + local match = 0 + os.execute("sleep 3") + local dlist = XQFunction.thrift_tunnel_to_datacenter([[{"api":503,"hidden":true}]]) + if dlist and dlist.code == 0 then + local completedList = dlist.completedList + local uncompletedList = dlist.uncompletedList + table.foreach(uncompletedList, function(i,v) table.insert(completedList, v) end) + for _,item in ipairs(completedList) do + if (dId and item.id == dId) or (not dId and item.address == downloadUrl) then + match = 1 + if not dId then + dId = item.id + XQPreference.set(XQConfigs.PREF_ROM_DOWNLOAD_ID, dId) + end + if lastsize == item.fileDownloadedSize then + nodata = nodata + 1 + else + lastsize = item.fileDownloadedSize + nodata = 0 + end + if item.datacenterErrorCode ~= 0 then + _resumeDownload(ids) + _deleteDownload(dId) + return false, nil, lastsize + elseif item.downloadStatus == 4 then + _resumeDownload(ids) + return XQCryptoUtil.md5File(item.localFileName), item.localFileName, item.fileDownloadedSize + elseif item.downloadStatus == 16 then + _resumeDownload(ids) + _deleteDownload(dId) + return false, nil, lastsize + elseif nodata > 60 then + _resumeDownload(ids) + _deleteDownload(dId) + XQLog.log(6, "xunlei download timeout, will switch to wget") + return false, nil, item.fileDownloadedSize + end + end + end + end + if match == 0 then + nomatch = nomatch + 1 + end + if nomatch > 60 then + _resumeDownload(ids) + _deleteDownload(dId) + XQLog.log(6, "xunlei download error, will switch to wget") + return false, nil, lastsize + end + end +end + +function syncDownload(downloadUrl, priority, smart) + if XQFunction.isStrNil(downloadUrl) then + return false + end + local smart = smart == nil and true or smart + XQLog.log(6, "Xunlei download start...") + local md5, filepath, size = _xunleiDownload(downloadUrl, priority) + if md5 and filepath then + return md5, filepath + end + if smart and md5 == false and size and size == 0 then + XQLog.log(6, "Xunlei download failed, start wget...") + md5 , filepath = _wgetDownload(downloadUrl) + if md5 then + XQLog.log(6, "Wget finished") + return md5, filepath + else + XQLog.log(6, "Wget failed") + end + end + return false +end + +function _xunleiDownloadPercent(downloadId) + local dlist = XQFunction.thrift_tunnel_to_datacenter([[{"api":503,"hidden":true}]]) + if dlist and dlist.code == 0 then + local completedList = dlist.completedList + local uncompletedList = dlist.uncompletedList + table.foreach(uncompletedList, function(i,v) table.insert(completedList, v) end) + for _,item in ipairs(completedList) do + if item.id == downloadId then + if item.downloadStatus == 4 then + return 100 + elseif item.downloadStatus == 16 then + return 0 + else + if item.fileTotalSize <= 0 then + return 0 + end + local percent = 100*item.fileDownloadedSize/item.fileTotalSize + if percent > 100 then + return 100 + else + return math.floor(percent) + end + end + end + end + return 0 + else + return 0 + end +end + +function _wgetDownloadPercent(downloadId) + local filepath = XQPreference.get(PREF_DOWNLOAD_FILE_PATH, nil) + local filesize = tonumber(XQPreference.get(XQConfigs.PREF_ROM_FULLSIZE, nil)) + if filepath and filesize and filesize > 0 then + local LuciFs = require("luci.fs") + local NixioFs = require("nixio.fs") + local percent + if NixioFs.access(filepath) then + local size = math.modf(LuciFs.stat(filepath).size) + percent = math.modf(size/filesize*100) + if percent < 1 and percent > 0 then + percent = 1 + elseif percent > 100 then + percent = 100 + end + end + return percent + end + return 0 +end + +function downloadPercent(downloadId) + local dtype = tonumber(XQPreference.get(PREF_DOWNLOAD_TYPE), nil) + if dtype then + if dtype == 1 then + return _xunleiDownloadPercent(downloadId) + elseif dtype == 2 then + return _wgetDownloadPercent(downloadId) + end + end + return 0 +end + +function _cancelXunleiDownload(downloadId) + _resumeDownload(XQPreference.get(XQConfigs.PREF_PAUSED_IDS, "")) + if XQFunction.isStrNil(downloadId) then + return false + else + local payload = { + ["api"] = 507, + ["idList"] = downloadId, + ["deletefile"] = true + } + local result = XQFunction.thrift_tunnel_to_datacenter(LuciJson.encode(payload)) + if result and result.code == 0 then + return true + else + return false + end + end +end + +function _cancelWgetDownload(downloadId) + for _, wgetPid in ipairs(LuciUtil.execl(WGET_PIDS)) do + if wgetPid then + os.execute("kill "..LuciUtil.trim(wgetPid)) + end + end + local LuciFs = require("luci.fs") + local filePath = XQPreference.get(PREF_DOWNLOAD_FILE_PATH, nil) + if filePath and LuciFs.access(filePath) then + LuciFs.unlink(filePath) + end + return true +end + +function cancelDownload(downloadUrl) + local dtype = tonumber(XQPreference.get(PREF_DOWNLOAD_TYPE), nil) + if dtype then + if dtype == 1 then + return _cancelXunleiDownload(downloadUrl) + elseif dtype == 2 then + return _cancelWgetDownload(downloadUrl) + end + end + return false +end diff --git a/Mi_Lua/xiaoqiang/util/XQErrorUtil.lua b/Mi_Lua/xiaoqiang/util/XQErrorUtil.lua new file mode 100644 index 0000000..7116c59 --- /dev/null +++ b/Mi_Lua/xiaoqiang/util/XQErrorUtil.lua @@ -0,0 +1,123 @@ +module ("xiaoqiang.util.XQErrorUtil", package.seeall) + +function getErrorMessage(errorCode) + local errorList = {} + -- 如果code大于1000 则是需要打印msg到客户端 + errorList[1501] = _("设置初始化状态失败") + errorList[1502] = _("输入不能够为空") + errorList[1503] = _("设置路由器名称失败") + errorList[1504] = _("检查更新失败") + errorList[1505] = _("文件MD5值校验失败,文件下载不完整") + errorList[1506] = _("没有可用更新") + errorList[1507] = _("文件不存在或者已经损坏,可以尝试重新下载") + errorList[1508] = _("MAC地址不合法") + errorList[1509] = _("文件上传失败") + errorList[1510] = _("文件不存在或者已经损坏") + errorList[1511] = _("不支持此语言") + errorList[1512] = _("文件上传到服务器失败") + errorList[1513] = _("恢复配置数据失败") + errorList[1514] = _("路由器名称设置失败") + errorList[1515] = _("路由器管理密码设置失败") + errorList[1516] = _("Wifi设置失败") + errorList[1517] = _("Wifi密码设置失败") + errorList[1518] = _("外网设置失败") + errorList[1519] = _("初始设置有未能成功") + errorList[1520] = _("Wifi密码不能少于8位") + errorList[1521] = _("Wifi密码应为8~63位") + errorList[1522] = _("Wifi密码应为5位或13位") + errorList[1523] = _("参数错误") + errorList[1524] = _("未能检测上网类型") + errorList[1525] = _("IP地址不合法") + errorList[1526] = _("局域网IP不能与外网IP在同一网段.") + errorList[1527] = _("局域网IP不合法,合法范围为10.0.0.0~10.255.255.255或172.16.0.0~172.23.255.255或192.168.0.0~192.168.255.255") + errorList[1528] = _("用户名或密码不能为空") + errorList[1529] = _("外网设置失败") + errorList[1530] = _("静态IP地址不合法") + errorList[1531] = _("子网掩码不合法") + errorList[1532] = _("默认网关不合法") + errorList[1533] = _("外网IP不合法,合法范围为1.0.0.0~126.0.0.0或128.0.0.0~223.255.255.255") + errorList[1534] = _("结束地址应高于起始地址") + errorList[1535] = _("IP范围应为2~254之间") + errorList[1536] = _("DHCP租用时间范围为2~2880分钟,或1~48小时") + errorList[1537] = _("参数有误") + errorList[1538] = _("登录失败") + errorList[1539] = _("未能获取到用户信息") + errorList[1540] = _("请求失败") + errorList[1541] = _("绑定当前路由器失败") + errorList[1542] = _("您的路由器还未绑定小米账号") + errorList[1543] = _("获取管理列表失败") + errorList[1544] = _("获取插件列表失败") + errorList[1545] = _("开启插件失败") + errorList[1546] = _("关闭插件失败") + errorList[1547] = _("获取插件信息失败") + errorList[1548] = _("该账号不具备此路由器的管理权限") + errorList[1549] = _("该小米账号已经解绑,您可以用管理账号登录并重新绑定") + errorList[1550] = _("解绑失败") + errorList[1551] = _("获取路由器管理列表失败,可能由于路由器系统时间不正确导致") + errorList[1552] = _("原密码不正确") + errorList[1553] = _("设置管理密码失败,密码不合法") + errorList[1554] = _("文件校验失败") + errorList[1555] = _("系统刷写失败") + errorList[1556] = _("账号验证失败,请检查网络是否正常密码是否正确") + errorList[1557] = _("密码不能够为空") + errorList[1558] = _("格式化硬盘失败") + errorList[1559] = _("datacenter服务工作不正常") + errorList[1560] = _("后台已经在执行刷写操作") + errorList[1561] = _("获取个人信息失败") + errorList[1562] = _("没有授权") + errorList[1563] = _("下载资源不可访问,可能是外网不通或者资源已经失效") + errorList[1564] = _("用户名或密码错误") + errorList[1565] = _("未能获取到授权信息,可能服务器异常") + errorList[1566] = _("网络繁忙,服务器无响应") + errorList[1567] = _("系统检测失败") + errorList[1568] = _("已经在执行系统升级,请等待") + errorList[1569] = _("设置数据访问权限失败") + errorList[1570] = _("上传文件太大,不能超过128M") + errorList[1571] = _("没有足够存储空间") + errorList[1572] = _("名字过长,请使用短一些的名字") + errorList[1573] = _("请输入标准字符,如 Xiaomi.Luyouqi") + errorList[1574] = _("未能获取到当前模式") + errorList[1575] = _("设置模式失败") + errorList[1576] = _("状态信息获取失败") + errorList[1577] = _("已经升级过,请手动重启路由器") + errorList[1578] = _("路由器已经没有足够的空间,上传失败") + errorList[1579] = _("已经在刷写固件,不可以取消") + errorList[1580] = _("该账号没有路由器的管理权限,您可以尝试退出重新登录") + errorList[1581] = _("服务器授权已过期,您需要重新登录") + errorList[1582] = _("nonce 验证错误") + errorList[1583] = _("VPN设置失败") + -- For vpn + errorList[1584] = _("用户名或密码验证失败") + errorList[1585] = _("无法连接服务器") + errorList[1586] = _("服务器连接异常,请重试") + errorList[1587] = _("该接口不允许外网访问") + errorList[1588] = _("测速失败,您可以尝试重新测试") + errorList[1589] = _("没有磁盘,请插入磁盘") + errorList[1590] = _("MTU不正确,合法范围为576 ~ 1498字节") + errorList[1591] = _("添加失败,最多只能添加32个设备") + errorList[1592] = _("IP/MAC地址不合法") + errorList[1593] = _("IP地址冲突,请更换IP地址") + errorList[1594] = _("MAC地址解除绑定失败") + errorList[1595] = _("开启失败,请检查开发者ID是否有效") + errorList[1600] = _("获取开发者信息失败") + errorList[1601] = _("关闭失败") + errorList[1602] = _("拨号状态获取失败") + errorList[1603] = _("认证失败(用户密码异常导致)") + errorList[1604] = _("无法连接服务器") + errorList[1605] = _("其他异常(协议不匹配等)") + errorList[1606] = _("操作失败") + errorList[1607] = _("QoS并未开启,禁止该操作") + errorList[1608] = _("端口号有冲突,请检查后重新设置") + errorList[1609] = _("由于DMZ功能开启,无法设置端口转发") + errorList[1610] = _("由于端口转发功能开启,无法设置DMZ") + errorList[1611] = _("工作模式冲突") + errorList[1612] = _("缺少参数") + errorList[1613] = _("参数中IP地址有冲突") + errorList[1614] = _("未能查询到该项信息") + errorList[1615] = _("访客WiFi设置失败,请检查无线名称和密码") + if (errorList[errorCode] == nil) then + return translate(_("未知错误")) + else + return translate(errorList[errorCode]) + end +end diff --git a/Mi_Lua/xiaoqiang/util/XQHttpUtil.lua b/Mi_Lua/xiaoqiang/util/XQHttpUtil.lua new file mode 100644 index 0000000..b735b72 --- /dev/null +++ b/Mi_Lua/xiaoqiang/util/XQHttpUtil.lua @@ -0,0 +1,108 @@ +module ("xiaoqiang.util.XQHttpUtil", package.seeall) + +local XQFunction = require("xiaoqiang.common.XQFunction") +local XQConfigs = require("xiaoqiang.common.XQConfigs") +local XQLog = require("xiaoqiang.XQLog") + +local Http = require("socket.http") +local Https = require("ssl.https") +local Ltn12 = require("luci.ltn12") + +function httpGetRequest(url, paramStr, cookies) + local header = {} + local cookieStr + local httpHandler + if cookies and type(cookies) == "table" then + cookieStr = "" + for key,value in pairs(cookies) do + cookieStr = cookieStr..key.."="..value..";path=/;domain=.xiaomi.com;" + end + header["Cookie"] = cookieStr + end + if url:match("^https://") then + httpHandler = Https + else + httpHandler = Http + end + + local result = { + code = "", + headers = "", + status = "", + res = "" + } + local res, code, headers, status + if XQFunction.isStrNil(cookieStr) then + if XQFunction.isStrNil(paramStr) then + res, code, headers, status = httpHandler.request(url) + else + res, code, headers, status = httpHandler.request(url, paramStr) + end + else + if not XQFunction.isStrNil(paramStr) then + local tmpUrl = url..paramStr + if tmpUrl:match("?") then + url = tmpUrl + else + url = url.."?"..paramStr + end + end + local t = {} + res, code, headers, status = httpHandler.request{ + url = url, + sink = Ltn12.sink.table(t), + headers = header + } + res = table.concat(t) + end + result.code = code or "" + result.headers = headers or "" + result.status = status or "" + result.res = res or "" + XQLog.log(7,result) + return result +end + +function httpPostRequest(url, paramStr, cookies) + local header = {} + local cookieStr + local httpHandler + if cookies and type(cookies) == "table" then + cookieStr = "" + for key,value in pairs(cookies) do + cookieStr = cookieStr..key.."="..value..";path=/;domain=.xiaomi.com;" + end + header["Cookie"] = cookieStr + end + header["Content-type"] = "application/x-www-form-urlencoded" + header["Content-length"] = string.len(paramStr) + + if url:match("^https://") then + httpHandler = Https + else + httpHandler = Http + end + + local result = { + code = "", + headers = "", + status = "", + res = "" + } + local t = {} + local res, code, headers, status = httpHandler.request{ + url = url, + method = "POST", + source = Ltn12.source.string(paramStr), + sink = Ltn12.sink.table(t), + headers = header + } + res = table.concat(t) + result.code = code or "" + result.headers = headers or "" + result.status = status or "" + result.res = res or "" + XQLog.log(7,result) + return result +end + diff --git a/Mi_Lua/xiaoqiang/util/XQLanWanUtil.lua b/Mi_Lua/xiaoqiang/util/XQLanWanUtil.lua new file mode 100644 index 0000000..ecba863 --- /dev/null +++ b/Mi_Lua/xiaoqiang/util/XQLanWanUtil.lua @@ -0,0 +1,962 @@ +module ("xiaoqiang.util.XQLanWanUtil", package.seeall) + +local XQFunction = require("xiaoqiang.common.XQFunction") +local XQConfigs = require("xiaoqiang.common.XQConfigs") + +function getDefaultMacAddress() + local LuciUtil = require("luci.util") + local mac = LuciUtil.exec(XQConfigs.GET_DEFAULT_MACADDRESS) + if XQFunction.isStrNil(mac) then + mac = nil + else + mac = LuciUtil.trim(mac):match("(%S-),") + end + return string.upper(mac) +end + +function getLanLinkList() + local LuciUtil = require("luci.util") + local lanLink = {} + local cmd = "et robord 0x01 0x00" + for _, line in ipairs(LuciUtil.execl(cmd)) do + local port,link = line:match('port (%d):(%S+)') + if link then + if tonumber(port) == 0 then + lanLink[1] = link == 'up' and 1 or 0 + end + if tonumber(port) == 2 then + lanLink[2] = link == 'up' and 1 or 0 + end + end + end + return lanLink +end + +function getLanIp() + local uci = require("luci.model.uci").cursor() + local lan = uci:get_all("network", "lan") + return lan.ipaddr +end + +--[[ +@return WANLINKSTAT=UP/DOWN +@return LOCALDNSSTAT=UP/DOWN +@return VPNLINKSTAT=UP/DOWN +]]-- +function getWanMonitorStat() + local NixioFs = require("nixio.fs") + local content = NixioFs.readfile(XQConfigs.WAN_MONITOR_STAT_FILEPATH) + local status = {} + if content ~= nil then + for line in string.gmatch(content, "[^\n]+") do + key,value = line:match('(%S+)=(%S+)') + status[key] = value + end + end + return status +end + +function getAutoWanType() + local LuciUtil = require("luci.util") + local result = LuciUtil.execi("/usr/sbin/wanlinkprobe 1 WAN pppoe dhcp") + local link,pppoe,dhcp + if result then + for line in result do + if line:match("^LINK=(%S+)") ~= nil then + link = line:match("^LINK=(%S+)") + elseif line:match("^PPPOE=(%S+)") ~= nil then + pppoe = line:match("^PPPOE=(%S+)") + elseif line:match("^DHCP=(%S+)") ~= nil then + dhcp = line:match("^DHCP=(%S+)") + end + end + end + if pppoe == "YES" then + return 1 + elseif dhcp == "YES" then + return 2 + elseif link ~= "YES" then + return 99 + else + return 0 + end +end + +function ubusWanStatus() + local ubus = require("ubus").connect() + local wan = ubus:call("network.interface.wan", "status", {}) + local result = {} + if wan["ipv4-address"] and #wan["ipv4-address"] > 0 then + result["ipv4"] = wan["ipv4-address"][1] + else + result["ipv4"] = { + ["mask"] = 0, + ["address"] = "" + } + end + result["dns"] = wan["dns-server"] or {} + result["proto"] = string.lower(wan.proto or "dhcp") + result["up"] = wan.up + result["uptime"] = wan.uptime or 0 + result["pending"] = wan.pending + result["autostart"] = wan.autostart + return result +end + +function _pppoeStatusCheck() + local LuciJson = require("json") + local LuciUtil = require("luci.util") + local cmd = "lua /usr/sbin/pppoe.lua status" + local status = LuciUtil.exec(cmd) + if status then + status = LuciUtil.trim(status) + if XQFunction.isStrNil(status) then + return false + end + status = LuciJson.decode(status) + return status + else + return false + end +end + +--- type +--- 1:认证失败(用户密码异常导致) +--- 2:无法连接服务器 +--- 3:其他异常(协议不匹配等) +function _pppoeErrorCodeHelper(code) + local errorA = { + ["507"] = 1,["691"] = 1,["509"] = 1,["514"] = 1,["520"] = 1, + ["646"] = 1,["647"] = 1,["648"] = 1,["649"] = 1,["691"] = 1, + ["646"] = 1 + } + local errorB = { + ["516"] = 1,["650"] = 1,["601"] = 1,["510"] = 1,["530"] = 1, + ["531"] = 1 + } + local errorC = { + ["501"] = 1,["502"] = 1,["503"] = 1,["504"] = 1,["505"] = 1, + ["506"] = 1,["507"] = 1,["508"] = 1,["511"] = 1,["512"] = 1, + ["515"] = 1,["517"] = 1,["518"] = 1,["519"] = 1 + } + local errcode = tostring(code) + if errcode then + if errorA[errcode] then + return 1 + end + if errorB[errcode] then + return 2 + end + if errorC[errcode] then + return 3 + end + return 1 + end +end + +--- status +--- 0:未拨号 +--- 1:正在拨号 +--- 2:拨号成功 +--- 3:正在拨号 但返现拨号错误信息 +--- 4:关闭拨号 +function getPPPoEStatus() + local result = {} + local status = ubusWanStatus() + if status then + local LuciNetwork = require("luci.model.network").init() + local network = LuciNetwork:get_network("wan") + if status.proto == "pppoe" then + if status.up then + result["status"] = 2 + else + local check = _pppoeStatusCheck() + if check then + if check.process == "down" then + result["status"] = 4 + elseif check.process == "up" then + result["status"] = 2 + elseif check.process == "connecting" then + if check.code == nil or check.code == 0 then + result["status"] = 1 + else + result["status"] = 3 + result["errcode"] = check.msg or "" + result["errtype"] = _pppoeErrorCodeHelper(tostring(check.code)) + end + end + else + result["status"] = 0 + end + end + local configdns = network:get_option_value("dns") + if not XQFunction.isStrNil(configdns) then + result["cdns"] = luci.util.split(configdns," ") + end + result["pppoename"] = network:get_option_value("username") + result["password"] = network:get_option_value("password") + result["peerdns"] = network:get_option_value("peerdns") + else + result["status"] = 0 + end + local device = network:get_interface() + local ipaddress = device:ipaddrs() + local ipv4 = { + ["address"] = "", + ["mask"] = "" + } + if ipaddress and #ipaddress > 0 then + ipv4["address"] = ipaddress[1]:host():string() + ipv4["mask"] = ipaddress[1]:mask():string() + end + result["ip"] = ipv4 + result["dns"] = status.dns + result["proto"] = status.proto + result["gw"] = network:gwaddr() or "" + return result + else + return false + end +end + +function pppoeStop() + os.execute("lua /usr/sbin/pppoe.lua down") +end + +function pppoeStart() + XQFunction.forkExec("lua /usr/sbin/pppoe.lua up") +end + +--[[ +@param interface : lan/wan +]]-- +function getLanWanInfo(interface) + if interface ~= "lan" and interface ~= "wan" then + return false + end + local LuciUtil = require("luci.util") + local LuciNetwork = require("luci.model.network").init() + local info = {} + local ipv6Dict = getIPv6Addrs() + local network = LuciNetwork:get_network(interface) + if network then + local device = network:get_interface() + local ipAddrs = device:ipaddrs() + local ip6Addrs = device:ip6addrs() + if interface == "wan" then + local mtuvalue = network:get_option_value("mtu") + if XQFunction.isStrNil(mtuvalue) then + mtuvalue = "1480" + end + info["mtu"] = tostring(mtuvalue) + info["details"] = getWanDetails() + -- 是否插了网线 + local cmd = 'et robord 0x01 0x00' + local data = LuciUtil.exec(cmd) + if not XQFunction.isStrNil(data) then + local linkStat = string.match(data, 'port 4:([^%s]+)') + info["link"] = linkStat == 'up' and 1 or 0; + end + end + if device and #ipAddrs > 0 then + local ipAddress = {} + for _,ip in ipairs(ipAddrs) do + ipAddress[#ipAddress+1] = {} + ipAddress[#ipAddress]["ip"] = ip:host():string() + ipAddress[#ipAddress]["mask"] = ip:mask():string() + end + info["ipv4"] = ipAddress + end + if device and #ip6Addrs > 0 then + local ipAddress = {} + for _,ip in ipairs(ip6Addrs) do + ipAddress[#ipAddress+1] = {} + ipAddress[#ipAddress]["ip"] = ip:host():string() + ipAddress[#ipAddress]["mask"] = ip:mask():string() + if ipv6Dict[ip] then + ipAddress[#ipAddress]["type"] = ipv6Dict[ip].type + end + end + info["ipv6"] = ipAddress + end + info["gateWay"] = network:gwaddr() + if network:dnsaddrs() then + info["dnsAddrs"] = network:dnsaddrs()[1] or "" + info["dnsAddrs1"] = network:dnsaddrs()[2] or "" + else + info["dnsAddrs"] = "" + info["dnsAddrs1"] = "" + end + if device and device:mac() ~= "00:00:00:00:00:00" then + info["mac"] = device:mac() + end + if info["mac"] == nil then + info["mac"] = getWanMac() + end + if network:uptime() > 0 then + info["uptime"] = network:uptime() + else + info["uptime"] = 0 + end + local status = network:status() + if status=="down" then + info["status"] = 0 + elseif status=="up" then + info["status"] = 1 + if info.details and info.details.wanType == "pppoe" then + wanMonitor = getWanMonitorStat() + if wanMonitor.WANLINKSTAT ~= "UP" then + info["status"] = 0 + end + end + elseif status=="connection" then + info["status"] = 2 + end + else + info = false + end + return info +end + +function getWanEth() + local LuciNetwork = require("luci.model.network").init() + local wanNetwork = LuciNetwork:get_network("wan") + return wanNetwork:get_option_value("ifname") +end + +function getWanMac() + local LuciUtil = require("luci.util") + local ifconfig = LuciUtil.exec("ifconfig " .. getWanEth()) + if not XQFunction.isStrNil(ifconfig) then + return ifconfig:match('HWaddr (%S+)') or "" + else + return nil + end +end + +--[[ +@param interface : lan/wan +]]-- +function getLanWanIp(interface) + if interface ~= "lan" and interface ~= "wan" then + return false + end + local LuciNetwork = require("luci.model.network").init() + local ipv4 = {} + local network = LuciNetwork:get_network(interface) + if network then + local device = network:get_interface() + local ipAddrs = device:ipaddrs() + if device and #ipAddrs > 0 then + for _,ip in ipairs(ipAddrs) do + ipv4[#ipv4+1] = {} + ipv4[#ipv4]["ip"] = ip:host():string() + ipv4[#ipv4]["mask"] = ip:mask():string() + end + end + end + return ipv4 +end + +function checkLanIp(ip) + local LuciIp = require("luci.ip") + local ipNl = LuciIp.iptonl(ip) + if (ipNl >= LuciIp.iptonl("10.0.0.0") and ipNl <= LuciIp.iptonl("10.255.255.255")) + or (ipNl >= LuciIp.iptonl("172.16.0.0") and ipNl <= LuciIp.iptonl("172.31.255.255")) + or (ipNl >= LuciIp.iptonl("192.168.0.0") and ipNl <= LuciIp.iptonl("192.168.255.255")) then + return 0 + else + return 1527 + end +end + +function setLanIp(ip,mask) + local XQEvent = require("xiaoqiang.XQEvent") + local LuciNetwork = require("luci.model.network").init() + local network = LuciNetwork:get_network("lan") + network:set("ipaddr",ip) + network:set("netmask",mask) + LuciNetwork:commit("network") + LuciNetwork:save("network") + XQEvent.lanIPChange(ip) + return true +end + +function getIPv6Addrs() + local LuciIp = require("luci.ip") + local LuciUtil = require("luci.util") + local cmd = "ifconfig|grep inet6" + local ipv6List = LuciUtil.execi(cmd) + local result = {} + for line in ipv6List do + line = luci.util.trim(line) + local ipv6,mask,ipType = line:match('inet6 addr: ([^%s]+)/([^%s]+)%s+Scope:([^%s]+)') + if ipv6 then + ipv6 = LuciIp.IPv6(ipv6,"ffff:ffff:ffff:ffff::") + ipv6 = ipv6:host():string() + result[ipv6] = {} + result[ipv6]['ip'] = ipv6 + result[ipv6]['mask'] = mask + result[ipv6]['type'] = ipType + end + end + return result +end + +function getLanDHCPService() + local LuciUci = require "luci.model.uci" + local lanDhcpStatus = {} + local uciCursor = LuciUci.cursor() + local ignore = uciCursor:get("dhcp", "lan", "ignore") + local leasetime = uciCursor:get("dhcp", "lan", "leasetime") + if ignore ~= "1" then + ignore = "0" + end + local leasetimeNum,leasetimeUnit = leasetime:match("^(%d+)([^%d]+)") + lanDhcpStatus["lanIp"] = getLanWanIp("lan") + lanDhcpStatus["start"] = uciCursor:get("dhcp", "lan", "start") + lanDhcpStatus["limit"] = uciCursor:get("dhcp", "lan", "limit") + lanDhcpStatus["leasetime"] = leasetime + lanDhcpStatus["leasetimeNum"] = leasetimeNum + lanDhcpStatus["leasetimeUnit"] = leasetimeUnit + lanDhcpStatus["ignore"] = ignore + return lanDhcpStatus +end + +--[[ +Set Lan DHCP, range = start~end +]]-- +function setLanDHCPService(startReq,endReq,leasetime,ignore) + local LuciUci = require("luci.model.uci") + local LuciUtil = require("luci.util") + local uciCursor = LuciUci.cursor() + if ignore == "1" then + uciCursor:set("dhcp", "lan", "ignore", tonumber(ignore)) + else + local limit = tonumber(endReq) - tonumber(startReq) + 1 + if limit < 0 then + return false + end + uciCursor:set("dhcp", "lan", "start", tonumber(startReq)) + uciCursor:set("dhcp", "lan", "limit", tonumber(limit)) + uciCursor:set("dhcp", "lan", "leasetime", leasetime) + uciCursor:delete("dhcp", "lan", "ignore") + end + uciCursor:save("dhcp") + uciCursor:load("dhcp") + uciCursor:commit("dhcp") + uciCursor:load("dhcp") + LuciUtil.exec("/etc/init.d/dnsmasq restart > /dev/null") + return true +end + +function wanDown() + local LuciUtil = require("luci.util") + LuciUtil.exec("env -i /sbin/ifdown wan") +end + +function wanRestart() + local LuciUtil = require("luci.util") + LuciUtil.exec("env -i /sbin/ifup wan") + XQFunction.forkExec("/etc/init.d/filetunnel restart") +end + +function dnsmsqRestart() + local LuciUtil = require("luci.util") + LuciUtil.exec("ubus call network reload; sleep 1; /etc/init.d/dnsmasq restart > /dev/null") +end + +--[[ +Get wan details, static ip/pppoe/dhcp/mobile +@return {proto="dhcp",ifname=ifname,dns=dns,peerdns=peerdns} +@return {proto="static",ifname=ifname,ipaddr=ipaddr,netmask=netmask,gateway=gateway,dns=dns} +@return {proto="pppoe",ifname=ifname,username=pppoename,password=pppoepasswd,dns=dns,peerdns=peerdns} +]]-- +function getWanDetails() + local LuciNetwork = require("luci.model.network").init() + local wanNetwork = LuciNetwork:get_network("wan") + local wanDetails = {} + if wanNetwork then + local wanType = wanNetwork:proto() + if wanType == "mobile" or wanType == "3g" then + wanType = "mobile" + elseif wanType == "static" then + wanDetails["ipaddr"] = wanNetwork:get_option_value("ipaddr") + wanDetails["netmask"] = wanNetwork:get_option_value("netmask") + wanDetails["gateway"] = wanNetwork:get_option_value("gateway") + elseif wanType == "pppoe" then + wanDetails["username"] = wanNetwork:get_option_value("username") + wanDetails["password"] = wanNetwork:get_option_value("password") + wanDetails["peerdns"] = wanNetwork:get_option_value("peerdns") + wanDetails["service"] = wanNetwork:get_option_value("service") + elseif wanType == "dhcp" then + wanDetails["peerdns"] = wanNetwork:get_option_value("peerdns") + end + if not XQFunction.isStrNil(wanNetwork:get_option_value("dns")) then + wanDetails["dns"] = luci.util.split(wanNetwork:get_option_value("dns")," ") + end + wanDetails["wanType"] = wanType + wanDetails["ifname"] = wanNetwork:get_option_value("ifname") + return wanDetails + else + return nil + end +end + +function generateDns(dns1, dns2, peerdns) + local dns + if not XQFunction.isStrNil(dns1) and not XQFunction.isStrNil(dns2) then + dns = {dns1,dns2} + elseif not XQFunction.isStrNil(dns1) then + dns = dns1 + elseif not XQFunction.isStrNil(dns2) then + dns = dns2 + end + return dns +end + +function checkMTU(value) + local mtu = tonumber(value) + if mtu and mtu >= 576 and mtu <= 1498 then + return true + else + return false + end +end + +function setWanPPPoE(name, password, dns1, dns2, peerdns, mtu, service) + local XQPreference = require("xiaoqiang.XQPreference") + local LuciNetwork = require("luci.model.network").init() + local uci = require("luci.model.uci").cursor() + local macaddr = uci:get("network", "wan", "macaddr") + + local iface = "wan" + local ifname = getWanEth() + local oldconf = uci:get_all("network", "wan") or {} + local wanrestart = true + local dnsrestart = true + if oldconf.username == name + and oldconf.password == password + and tonumber(oldconf.mtu) == tonumber(mtu) + and ((XQFunction.isStrNil(oldconf.service) and XQFunction.isStrNil(service)) or oldconf.service == service) then + wanrestart = false + end + local dnss = {} + local odnss = {} + if oldconf.dns and type(oldconf.dns) == "string" then + odnss = {oldconf.dns} + elseif oldconf.dns and type(oldconf.dns) == "table" then + odnss = oldconf.dns + end + if not XQFunction.isStrNil(dns1) then + table.insert(dnss, dns1) + end + if not XQFunction.isStrNil(dns2) then + table.insert(dnss, dns2) + end + if #dnss == #odnss then + if #dnss == 0 then + dnsrestart = false + else + local odnsd = {} + local match = 0 + for _, dns in ipairs(odnss) do + odnsd[dns] = 1 + end + for _, dns in ipairs(dnss) do + if odnsd[dns] == 1 then + match = match + 1 + end + end + if match == #dnss then + dnsrestart = false + end + end + end + + local wanNet = LuciNetwork:del_network(iface) + local mtuvalue + if mtu then + if checkMTU(mtu) then + mtuvalue = tonumber(mtu) + else + return false + end + else + mtuvalue = 1480 + end + wanNet = LuciNetwork:add_network( + iface, { + proto ="pppoe", + ifname = ifname, + username = name, + password = password, + dns = generateDns(dns1,dns2,peerdns), + peerdns = peerdns, + macaddr = macaddr, + service = service, + mtu = mtuvalue + }) + if not XQFunction.isStrNil(name) then + XQPreference.set(XQConfigs.PREF_PPPOE_NAME,name) + XQFunction.nvramSet("nv_pppoe_name", name) + end + if not XQFunction.isStrNil(password) then + XQPreference.set(XQConfigs.PREF_PPPOE_PASSWORD,password) + XQFunction.nvramSet("nv_pppoe_pwd", password) + end + XQFunction.nvramSet("nv_wan_type", "pppoe") + XQFunction.nvramCommit() + if wanNet then + LuciNetwork:save("network") + LuciNetwork:commit("network") + os.execute(XQConfigs.VPN_DISABLE) + if dnsrestart then + dnsmsqRestart() + end + if wanrestart then + wanRestart() + else + local pppoestatus = getPPPoEStatus() + if pppoestatus and pppoestatus.status == 4 then + pppoeStart() + end + end + return true + else + return false + end +end + +function checkWanIp(ip) + local LuciIp = require("luci.ip") + local ipNl = LuciIp.iptonl(ip) + if (ipNl >= LuciIp.iptonl("1.0.0.0") and ipNl <= LuciIp.iptonl("126.0.0.0")) + or (ipNl >= LuciIp.iptonl("128.0.0.0") and ipNl <= LuciIp.iptonl("223.255.255.255")) then + return 0 + else + return 1533 + end +end + +function setWanStaticOrDHCP(ipType, ip, mask, gw, dns1, dns2, peerdns, mtu) + local LuciNetwork = require("luci.model.network").init() + local uci = require("luci.model.uci").cursor() + local macaddr = uci:get("network", "wan", "macaddr") + local oldconf = uci:get_all("network", "wan") or {} + + local iface = "wan" + local ifname = getWanEth() + local dnsrestart = true + local wanrestart = true + local dnss = {} + local odnss = {} + if oldconf.dns and type(oldconf.dns) == "string" then + odnss = {oldconf.dns} + elseif oldconf.dns and type(oldconf.dns) == "table" then + odnss = oldconf.dns + end + if not XQFunction.isStrNil(dns1) then + table.insert(dnss, dns1) + end + if not XQFunction.isStrNil(dns2) then + table.insert(dnss, dns2) + end + if #dnss == #odnss then + if #dnss == 0 then + dnsrestart = false + else + local odnsd = {} + local match = 0 + for _, dns in ipairs(odnss) do + odnsd[dns] = 1 + end + for _, dns in ipairs(dnss) do + if odnsd[dns] == 1 then + match = match + 1 + end + end + if match == #dnss then + dnsrestart = false + end + end + end + local wanNet = LuciNetwork:del_network(iface) + local dns = generateDns(dns1, dns2, peerdns) + local mtuvalue + if mtu then + mtuvalue = tonumber(mtu) + end + if ipType == "dhcp" then + if oldconf.proto == "dhcp" then + wanrestart = false + end + local network = { + proto = "dhcp", + ifname = ifname, + dns = dns, + macaddr = macaddr, + peerdns = peerdns + } + if mtu then + network["mtu"] = mtuvalue + end + wanNet = LuciNetwork:add_network(iface, network) + elseif ipType == "static" then + if oldconf.proto == "static" + and oldconf.ipaddr == ip + and oldconf.netmask == mask + and oldconf.gateway == gw + and oldconf.mtu == mtuvalue then + wanrestart = false + end + if not dns then + dns = gw + end + local network = { + proto = "static", + ipaddr = ip, + netmask = mask, + gateway = gw, + dns = dns, + macaddr = macaddr, + ifname = ifname + } + if mtu then + network["mtu"] = mtuvalue + end + wanNet = LuciNetwork:add_network(iface, network) + end + XQFunction.nvramSet("nv_wan_type", ipType) + XQFunction.nvramCommit() + if wanNet then + LuciNetwork:save("network") + LuciNetwork:commit("network") + if dnsrestart then + dnsmsqRestart() + end + if wanrestart then + wanRestart() + end + return true + else + return false + end +end + +function setWanMac(mac) + local LuciNetwork = require("luci.model.network").init() + local LuciDatatypes = require("luci.cbi.datatypes") + local network = LuciNetwork:get_network("wan") + local oldMac = network:get_option_value("macaddr") + local succeed = false + if oldMac ~= mac then + if XQFunction.isStrNil(mac) then + local defaultMac = getDefaultMacAddress() or "" + network:set("macaddr",defaultMac) + succeed = true + elseif LuciDatatypes.macaddr(mac) and mac ~= "ff:ff:ff:ff:ff:ff" and mac ~= "00:00:00:00:00:00" then + network:set("macaddr",mac) + succeed = true + end + else + succeed = true + end + if succeed then + LuciNetwork:save("network") + LuciNetwork:commit("network") + wanRestart() + end + return succeed +end + +function _checkIP(ip) + if XQFunction.isStrNil(ip) then + return false + end + local LuciIp = require("luci.ip") + local ipNl = LuciIp.iptonl(ip) + if (ipNl >= LuciIp.iptonl("1.0.0.0") and ipNl <= LuciIp.iptonl("126.0.0.0")) + or (ipNl >= LuciIp.iptonl("128.0.0.0") and ipNl <= LuciIp.iptonl("223.255.255.255")) then + return true + else + return false + end +end + +function _checkMac(mac) + if XQFunction.isStrNil(mac) then + return false + end + local LuciDatatypes = require("luci.cbi.datatypes") + if LuciDatatypes.macaddr(mac) and mac ~= "ff:ff:ff:ff:ff:ff" and mac ~= "00:00:00:00:00:00" then + return true + else + return false + end +end + +function _parseMac(mac) + if mac then + return string.lower(string.gsub(mac,"[:-]","")) + else + return nil + end +end + +function _parseDhcpLeases() + local NixioFs = require("nixio.fs") + local uci = require("luci.model.uci").cursor() + local result = {} + local leasefile = XQConfigs.DHCP_LEASE_FILEPATH + uci:foreach("dhcp", "dnsmasq", + function(s) + if s.leasefile and NixioFs.access(s.leasefile) then + leasefile = s.leasefile + return false + end + end) + local dhcp = io.open(leasefile, "r") + if dhcp then + for line in dhcp:lines() do + if line then + local ts, mac, ip, name = line:match("^(%d+) (%S+) (%S+) (%S+)") + if name == "*" then + name = "" + end + if ts and mac and ip and name then + result[ip] = { + mac = string.lower(XQFunction.macFormat(mac)), + ip = ip, + name = name + } + end + end + end + dhcp:close() + end + return result +end + +-- +-- Event +-- +function hookLanIPChangeEvent(ip) + if XQFunction.isStrNil(ip) then + return + end + local uci = require("luci.model.uci").cursor() + local lan = ip:gsub(".%d+$","") + uci:foreach("macbind", "host", + function(s) + local ip = s.ip + ip = lan.."."..ip:match(".(%d+)$") + uci:set("macbind", s[".name"], "ip", ip) + end + ) + uci:foreach("dhcp", "host", + function(s) + local ip = s.ip + ip = lan.."."..ip:match(".(%d+)$") + uci:set("dhcp", s[".name"], "ip", ip) + end + ) + uci:commit("dhcp") + uci:commit("macbind") +end + +--- Tag +--- 0:未添加 +--- 1:已添加 +--- 2:已绑定 +function macBindInfo() + local uci = require("luci.model.uci").cursor() + local info = {} + uci:foreach("macbind", "host", + function(s) + local item = { + ["name"] = s.name, + ["mac"] = s.mac, + ["ip"] = s.ip, + ["tag"] = 1 + } + info[s.mac] = item + end + ) + uci:foreach("dhcp", "host", + function(s) + local item = { + ["name"] = s.name, + ["mac"] = s.mac, + ["ip"] = s.ip, + ["tag"] = 2 + } + info[s.mac] = item + end + ) + return info +end + +--- 0:设置成功 +--- 1:IP冲突 +--- 2:MAC/IP 不合法 +function addBind(mac, ip) + local uci = require("luci.model.uci").cursor() + if _checkIP(ip) and _checkMac(mac) then + local dhcp = _parseDhcpLeases() + mac = string.lower(XQFunction.macFormat(mac)) + local host = dhcp[ip] + if host and host.mac ~= mac then + return 1 + end + local name = _parseMac(mac) + local options = { + ["name"] = name, + ["mac"] = mac, + ["ip"] = ip + } + uci:section("macbind", "host", name, options) + uci:commit("macbind") + else + return 2 + end + return 0 +end + +function removeBind(mac) + local uci = require("luci.model.uci").cursor() + if _checkMac(mac) then + local name = _parseMac(mac) + uci:delete("macbind", name) + uci:delete("dhcp", name) + uci:commit("macbind") + uci:commit("dhcp") + return true + else + return false + end +end + +function unbindAll() + local uci = require("luci.model.uci").cursor() + uci:delete_all("dhcp", "host") + uci:delete_all("macbind", "host") + uci:commit("dhcp") + uci:commit("macbind") +end + +function saveBindInfo() + local uci = require("luci.model.uci").cursor() + uci:delete_all("dhcp", "host") + uci:foreach("macbind", "host", + function(s) + local options = { + ["name"] = s.name, + ["mac"] = s.mac, + ["ip"] = s.ip + } + uci:section("dhcp", "host", s.name, options) + end + ) + uci:commit("dhcp") +end \ No newline at end of file diff --git a/Mi_Lua/xiaoqiang/util/XQMitvUtil.lua b/Mi_Lua/xiaoqiang/util/XQMitvUtil.lua new file mode 100644 index 0000000..4616266 --- /dev/null +++ b/Mi_Lua/xiaoqiang/util/XQMitvUtil.lua @@ -0,0 +1,165 @@ +module ("xiaoqiang.util.XQMitvUtil", package.seeall) + +local JSON = require("luci.json") +local LuciProtocol = require("luci.http.protocol") +local urlencode = LuciProtocol.urlencode +local XQLog = require("xiaoqiang.XQLog") + +function DoExec(cmd) + local LuciUtil = require("luci.util") + XQLog.log(7,cmd) + local s = LuciUtil.exec(cmd) + -- XQLog.log(7,s) + return s +end + +--[[ +local JSON = require("json") +function urlencode(str) + if (str) then + str = string.gsub (str, "\n", "\r\n") + str = string.gsub (str, "([^%w %-%_%.%~])", + function (c) return string.format ("%%%02X", string.byte(c)) end) + str = string.gsub (str, " ", "+") + end + return str +end + +function DoExec(cmd) + -- print(cmd) + local f = assert(io.popen(cmd, 'r')) + local s = assert(f:read('*a')) + f:close() + return s +end +]] + + +-- +-- mitv control api wrapper +-- zhangyanlu@xiaomi.com +-- + +local Errorm1 = "{ \"code\" : -1 , \"msg\" : \"api not exists\" }"; +local Error3 = "{ \"code\" : 3 , \"msg\" : \"parameter format error\" }"; +local Error4 = "{ \"code\" : 4 , \"msg\" : \"mitv api result error\" }"; + +function request(payload) + -- payload example : { "mac|ip" : "", "command" : "keyevent", "keycode" : "left" } + if payload == nil then + return Error3 + end + + local params = JSON.decode(payload) + if params == nil then + return Error3 + end + local ip = params.ip + if ip == nil then + if params.mac == nil then + return Error3 + end + -- get ip from mac + local DeviceUtil = require("xiaoqiang.util.XQDeviceUtil") + local devices = DeviceUtil.getDHCPDict() + local item = devices[params.mac] + if item == nil then + return Error3 + end + ip = item.ip + end + if not string.match(ip,"^%d+%.%d+%.%d+%.%d+$") then + return Error3 + end + + if params.command == "isalive" then + return isalive(ip) + elseif params.command == "keyevent" then + return control(ip,params.keycode) + elseif params.command == "video_playurl" then + return playVideoByUrl(ip,params.url) + elseif params.command == "video_playmediaid" then + return playVideoByMediaid(ip,params.mediaid,params.ci) + elseif params.command == "music_playurl" then + return playMusicByUrl(ip,params.url) + elseif params.command == "photo_playurl" then + return playPhotoByUrl(ip,params.url) + else + return Errorm1 + end + +end + +-- mitv api returns status=0 , miwifi apis uses code=0 +function castMitvResult(str) + if str == nil or str == "" then + return Error4 + end + local res = JSON.decode(str) + res.code = res.status + return JSON.encode(res) +end + +-- test if mitv is alive +-- use small timeout +function isalive(ip) + local cmd = "curl -s -k --connect-timeout 1 \"http://%s:6095/request?action=isalive\"" + local result = DoExec(string.format(cmd,ip)) + + return castMitvResult(result) +end + +-- control mitv +-- keycode enter, back, up, down, left, right, menu, home, voluemup, volumedown, power +-- aware of bash injection +function control(ip,keycode) + if not string.match(keycode,"^%a+$") then + return Error3 + end + local cmd = "curl -s -k \"http://%s:6095/controller?action=keyevent&keycode=%s\"" + local result = DoExec(string.format(cmd,ip,keycode)) + return castMitvResult(result) +end + +function playVideoByUrl(ip,url) + local cmd = "curl -s -k \"http://%s:6095/video?action=play&url=%s&clientname=miwifi\"" + local result = DoExec(string.format(cmd,ip,urlencode(url))) + return castMitvResult(result) +end + +function playVideoByMediaid(ip,mediaid,ci) + if not string.match(mediaid,"^%d+$") or not string.match(ci,"^%d+$") then + return Error3 + end + local cmd = "curl -s -k \"http://%s:6095/video?action=play&mediaid=%s&ci=%s&prefersource=1&clientname=miwifi\"" + local result = DoExec(string.format(cmd,ip,mediaid,ci)) + return castMitvResult(result) +end + +function playMusicByUrl(ip,url) + local cmd = "curl -s -k \"http://%s:6095/music?action=play&url=%s&clientname=miwifi\"" + local result = DoExec(string.format(cmd,ip,urlencode(url))) + return castMitvResult(result) +end + + +function playPhotoByUrl(ip,url) + local cmd = "curl -s -k \"http://%s:6095/photo?action=play&url=%s&clientname=miwifi\"" + local result = DoExec(string.format(cmd,ip,urlencode(url))) + return castMitvResult(result) +end + + +-- print(request()) +-- print(request("")) +-- print(request("{ \"ip\" : \"192.168.32.109\" , \"command\" : \"isalive\"}")) +-- print(request("{ \"ip\" : \"192.168.32.109\" , \"command\" : \"keyevent\" , \"keycode\" : \"& sudo ls\"}")) +-- print(request("{ \"ip\" : \"192.168.32.109\" , \"command\" : \"keyevent\" , \"keycode\" : \"right\"}")) + +-- print(request("{ \"ip\" : \"192.168.32.109\" , \"command\" : \"keyevent\" , \"keycode\" : \"back\"}")) +-- print(request("{ \"ip\" : \"192.168.32.109\" , \"command\" : \"video_playurl\" , \"url\" : \"http://wifi.io/video.mp4\"}")) + +-- print(request("{ \"ip\" : \"192.168.32.109\" , \"command\" : \"video_playmediaid\" , \"mediaid\" : \"87931\" , \"ci\" : \"1\" }")) + +-- print(request("{ \"ip\" : \"192.168.32.109\" , \"command\" : \"music_playurl\" , \"url\" : \"http://yinyueshiting.baidu.com/data2/music/108215801/14385500158400128.mp3?xcode=191fb7d6bc00415844edb509f9a331dfa888a58d2b9e82c6\"}")) +-- print(request("{ \"ip\" : \"192.168.32.109\" , \"command\" : \"photo_playurl\" , \"url\" : \"http://img03.mifile.cn/webfile/images/hd/2014040802/www/ac_12.jpg\"}")) \ No newline at end of file diff --git a/Mi_Lua/xiaoqiang/util/XQNetUtil.lua b/Mi_Lua/xiaoqiang/util/XQNetUtil.lua new file mode 100644 index 0000000..f643379 --- /dev/null +++ b/Mi_Lua/xiaoqiang/util/XQNetUtil.lua @@ -0,0 +1,679 @@ +module ("xiaoqiang.util.XQNetUtil", package.seeall) + +local XQFunction = require("xiaoqiang.common.XQFunction") +local XQConfigs = require("xiaoqiang.common.XQConfigs") + +local LuciProtocol = require("luci.http.protocol") +local XQHttpUtil = require("xiaoqiang.util.XQHttpUtil") + +local DEFAULT_TOKEN = "8007236f-a2d6-4847-ac83-c49395ad6d65" +local V3_TOKEN + +function getToken() + return V3_TOKEN +end + +function getMacAddr() + local XQLanWanUtil = require("xiaoqiang.util.XQLanWanUtil") + return XQLanWanUtil.getDefaultMacAddress() +end + +function getSN() + local LuciUtil = require("luci.util") + local sn = LuciUtil.exec(XQConfigs.GET_NVRAM_SN) + if XQFunction.isStrNil(sn) then + return nil + else + sn = LuciUtil.trim(sn) + end + return sn +end + +function getUserAgent() + local sn = getSN() or "" + return "miwifi-"..sn +end + +function cryptUrl(serverUrl, subUrl, params, salt) + local XQCryptoUtil = require("xiaoqiang.util.XQCryptoUtil") + if serverUrl == nil or params == nil then + return nil + end + local time = XQFunction.getTime() + table.insert(params,{"time",time}) + table.sort(params, function(a, b) return a[1] < b[1] end) + local str = "" + table.foreach(params, function(k, v) str = str..v[1].."="..v[2].."&" end) + if salt ~= nil and salt ~= "" then + str = str .. salt + end + local md5 = XQCryptoUtil.md5Base64Str(str) + local token = getToken() + local isV2 = string.find(serverUrl..subUrl,"/v2/") ~= nil or string.find(serverUrl..subUrl,"/rs/") ~= nil + if XQFunction.isStrNil(token) or isV2 then + token = DEFAULT_TOKEN + end + + local url = "" + if string.find(serverUrl..subUrl,"?") == nil then + url = serverUrl..subUrl.."?s="..md5.."&time="..time.."&token="..LuciProtocol.urlencode(token) + else + url = serverUrl..subUrl.."&s="..md5.."&time="..time.."&token="..LuciProtocol.urlencode(token) + end + + return url +end + +-- +-- HBase +-- +local HBASE_LOG_UPLOAD_URL = "https://data.gorouter.info/xiaoqiang_log/" +local HBASE_CONFIG_UPLOAD_URL = "https://data.gorouter.info/xiaoqiang_config/" +local SUB_KEY = "false-row-key" +local CURL_CMD = "curl -k -i -f -X PUT %s%s -H \"Content-Type: application/json\" --data @%s 2>/dev/null" +local CONFIG_KEY = "Qzo=" + +local LOG_TYPE = { + ["M"] = "TTo=", + ["B"] = "Qjo=", + ["X"] = "WDo=", + ["Y"] = "WTo=", + ["Z"] = "Wjo=" +} + +--[[ + M:Network detection log + B:System log + X:Not use + Y:Not use + Z:Not use +]]-- +function uploadLogFile(logfile,logtype) + local Mime = require("mime") + local LuciJson = require("json") + local uuid = getMacAddr() + if uuid == nil then + return false + end + local logKey = LOG_TYPE[logtype] + if XQFunction.isStrNil(logKey) then + return false + end + local filebase64str = luci.util.exec("/usr/bin/base64 "..logfile) + local timestamp = string.format("%012d",os.time()) + local key = Mime.b64(uuid.."-"..timestamp) + local blob = {["column"]=logKey, ["$"]=string.gsub(filebase64str,"\n","")} + local cell = {blob} + local item = {["key"]=key,["Cell"]=cell} + local data = {["Row"]=item} + local dataStr = LuciJson.encode(data) + local logJsonFile = io.open(XQConfigs.XQ_LOG_JSON_FILEPATH,"w") + if logJsonFile then + logJsonFile:write(dataStr) + logJsonFile:close() + end + local command = string.format(CURL_CMD,HBASE_LOG_UPLOAD_URL,SUB_KEY,XQConfigs.XQ_LOG_JSON_FILEPATH) + local result = luci.util.exec(command) + if result == nil or result == "" then + return false + else + if string.find(result,"OK") ~= nil then + return true + else + return false + end + end +end + +function uploadConfigFile(configfile) + local Mime = require("mime") + local LuciJson = require("json") + local uuid = getMacAddr() + if uuid == nil then + return false + end + local filebase64str = luci.util.exec("/usr/bin/base64 "..configfile) + local key = Mime.b64(uuid) + local config = {["column"]=CONFIG_KEY, ["$"]=string.gsub(filebase64str,"\n","")} + local cell = {config} + local item = {["key"]=key,["Cell"]=cell} + local data = {["Row"]=item} + local dataStr = LuciJson.encode(data) + local configJsonFile = io.open(XQConfigs.XQ_CONFIG_JSON_FILEPATH,"w") + if configJsonFile then + configJsonFile:write(dataStr) + configJsonFile:close() + end + local command = string.format(CURL_CMD,HBASE_CONFIG_UPLOAD_URL,SUB_KEY,XQConfigs.XQ_CONFIG_JSON_FILEPATH) + local result = luci.util.exec(command) + if result == nil or result == "" then + return false + else + if string.find(result,"OK") ~= nil then + return true + else + return false + end + end +end + +--[[ +@param configfile: save path +unzip: invalid option -- t +]]-- +function getConfigFile(configfile) + local Xml = require("slaxdom") + local SSLHttps = require("ssl.https") + local XQCryptoUtil = require("xiaoqiang.util.XQCryptoUtil") + local uuid = getMacAddr() + if uuid == nil then + return false + end + local url = HBASE_CONFIG_UPLOAD_URL..uuid + local xmlstr = SSLHttps.request(url) + if xmlstr == nil or xmlstr == "" then + return false + end + if string.find(xmlstr,"html") ~= nil then + return false + end + local obj = Xml:dom(xmlstr) + local arr = obj.root.el[1].el + if #arr == 0 then + return false + end + local record = arr[1].kids[1].value + local decstr = XQCryptoUtil.binaryBase64Dec(record) + fd = io.open(configfile, "w") + fd:write(decstr) + fd:close() + local check = luci.util.exec("unzip -o "..configfile) + luci.util.exec("rm -rf /tmp/etc/") + if string.find(check,"invalid") ~= nil or string.find(check,"inflate error") ~= nil then + return false + end + return true +end + +-- +-- Xiaomi V3 +-- +local ACCOUNT_DOMAIN_ONLINE = "https://account.xiaomi.com/" +local ACCOUNT_DOMAIN_STAGING = "http://account.preview.n.xiaomi.net/" +local PASSPORT_LOGIN_PWD_URL = "pass/serviceLoginAuth" +local PASSPORT_LOGIN_PASSTOKEN_URL = "pass/serviceLogin?sid=xiaoqiang" + +function xiaomiLogin(username,password) + local LuciJson = require("json") + local XQLog = require("xiaoqiang.XQLog") + local XQDBUtil = require("xiaoqiang.util.XQDBUtil") + local XQSysUtil = require("xiaoqiang.util.XQSysUtil") + local XQCryptoUtil = require("xiaoqiang.util.XQCryptoUtil") + local userId,nonce,passToken,ssecurity,psecurity,location,domain + -- + -- step1 get passtoken security and nonce /https + -- + local params = { + {"user", username}, + {"hash", string.upper(password)}, + {"sid", "xiaoqiang"}, + {"deviceId", getSN() or ""} + } + local url + if XQConfigs.SERVER_CONFIG == 1 then + url = ACCOUNT_DOMAIN_STAGING..PASSPORT_LOGIN_PWD_URL + else + url = ACCOUNT_DOMAIN_ONLINE..PASSPORT_LOGIN_PWD_URL + end + local data = "" + table.foreach(params, function(k, v) data = data..v[1].."="..v[2].."&" end) + local step1 = XQHttpUtil.httpGetRequest(url,string.sub(data,1,-2)) + if step1.code == 302 then + local extensionPragma = step1.headers["extension-pragma"] + local setCookie = step1.headers["set-cookie"] + local extensionJson = LuciJson.decode(extensionPragma) + + location = step1.headers["location"] + userId = setCookie:match('userId=(%d+);') + passToken = setCookie:match('passToken=(%S+);') + domain = setCookie:match('domain=(%S+);') + nonce = extensionPragma:match('%S+\"nonce\":(%d+),%S+') + ssecurity = extensionJson["ssecurity"] + psecurity = extensionJson["psecurity"] + XQLog.log(7,"XiaomiLogin Step1 Succeed:",step1) + + -- + -- step2 get xiaoqiang server token + -- + local tobeSign = "nonce="..nonce.."&"..ssecurity + local sign = XQCryptoUtil.binaryBase64Enc(XQCryptoUtil.sha1Binary(tobeSign)) + local queryStr = LuciProtocol.xq_urlencode_params({ + ["uuid"] = userId, + ["clientSign"] = sign + }) + local step2url = location.."&"..queryStr + local step2 = XQHttpUtil.httpGetRequest(step2url) + local stoken + if step2.code == 200 and type(step2.headers) == "table" then + local xqcookie = step2.headers["set-cookie"] + if xqcookie then + stoken = xqcookie:match('serviceToken=(%S+);') + end + elseif step2.code == 401 then + XQLog.log(3,"XiaomiLogin Step2 401 Failed:", step2url, step2) + return {code = 2} + end + local sid = ssecurity + if not XQFunction.isStrNil(userId) and + not XQFunction.isStrNil(passToken) and + not XQFunction.isStrNil(stoken) and + not XQFunction.isStrNil(sid) and + not XQFunction.isStrNil(ssecurity) then + XQLog.log(7,"XiaomiLogin Step2 succeed:"..userId) + XQDBUtil.savePassport(userId,passToken,stoken,sid,ssecurity) + local result = { + code = 0, + uuid = userId, + token = passToken, + stoken = stoken, + sid = sid, + ssecurity = ssecurity + } + return result + else + XQLog.log(3,"XiaomiLogin Step2 Failed:",{ + step2url = step2url, + userId = userId or "", + passToken = passToken or "", + ssecurity = ssecurity or "" + }) + return {code = 2} + end + elseif step1.code == 200 then + XQLog.log(3,"XiaomiLogin Step1 Username/Password Error:", params, step1) + return {code = 1} + else + XQLog.log(3,"XiaomiLogin Step1 Service Unreachable:", params, step1) + return {code = 3} + end +end + +function getPassport(uuid) + local XQSysUtil = require("xiaoqiang.util.XQSysUtil") + local XQDBUtil = require("xiaoqiang.util.XQDBUtil") + if XQFunction.isStrNil(uuid) then + uuid = XQSysUtil.getBindUUID() + end + if XQFunction.isStrNil(uuid) then + return false + end + local passport = XQDBUtil.fetchPassport(uuid)[1] + if not passport then + return false + end + if XQFunction.isStrNil(passport.token) then + return false + end + V3_TOKEN = passport.token + return passport +end + +function generateOrigIconUrl(iconUrl) + if XQFunction.isStrNil(iconUrl) then + return "" + else + return string.gsub(iconUrl,".jpg","_150.jpg") + end +end + +local GET_USER_INFO = "http://api.account.xiaomi.com/pass/usersCard?ids=" +function getUserInfo(uuid) + local LuciJson = require("json") + local XQSysUtil = require("xiaoqiang.util.XQSysUtil") + if XQFunction.isStrNil(uuid) then + uuid = XQSysUtil.getBindUUID() + if XQFunction.isStrNil(uuid) then + return false + end + end + local result = XQHttpUtil.httpGetRequest(GET_USER_INFO..uuid) + if result.code ~= 200 then + return false + end + local resultJson = LuciJson.decode(result.res) + if resultJson then + if string.upper(resultJson.result) == "OK" then + local data = resultJson.data.list + if data[1] then + local userInfo = {} + userInfo["aliasNick"] = data[1].aliasNick or "" + userInfo["miliaoNick"] = data[1].miliaoNick or "" + userInfo["userId"] = data[1].userId or "" + userInfo["miliaoIcon"] = data[1].miliaoIcon or "" + userInfo["miliaoIconOrig"] = generateOrigIconUrl(userInfo.miliaoIcon) + XQSysUtil.setBindUserInfo(userInfo) + return userInfo + end + end + end + return false +end + +-- +-- XiaoQiang +-- +local XIAOQIANG_SERVER = XQConfigs.SERVER_CONFIG_ONLINE_URL +if XQConfigs.SERVER_CONFIG == 1 then + XIAOQIANG_SERVER = XQConfigs.SERVER_CONFIG_STAGING_URL +elseif XQConfigs.SERVER_CONFIG == 2 then + XIAOQIANG_SERVER = XQConfigs.SERVER_CONFIG_PREVIEW_URL +end +-- GET +local XIAOQIANG_UPGRADE = "/rs/grayupgrade" +local XIAOQIANG_RECOVERY_UPGRADE = "/rs/grayupgrade/recovery" +local XIAOQIANG_DEVICELIST = "/s/admin/deviceList" +local XIAOQIANG_ADMINLIST = "/s/device/adminList" +-- POST +local XIAOQIANG_REGISTER = "/s/register" +local XIAOQIANG_PROMOTE = "/s/admin/promote" +local XIAOQIANG_DISMISS = "/s/admin/dismiss" +local XIAOQIANG_PLUGIN_ENABLE = "/s/plugin/enable" +local XIAOQIANG_PLUGIN_DISABLE = "/s/plugin/disable" +local XIAOQIANG_PLUGIN_LIST = "/s/plugin/list" +local XIAOQIANG_PLUGIN_DETAIL = "/s/plugin/detail" +local XIAOQIANG_UPDATE_DEVICENAME = "/s/device/name" + +function getDeviceId() + local LuciUtil = require("luci.util") + local deviceId = LuciUtil.exec(XQConfigs.XQ_DEVICE_ID) + if XQFunction.isStrNil(deviceId) then + deviceId = "" + end + return LuciUtil.trim(deviceId) +end + +function checkUpgrade() + local LuciJson = require("json") + local XQSysUtil = require("xiaoqiang.util.XQSysUtil") + local XQPreference = require("xiaoqiang.XQPreference") + local XQCryptoUtil = require("xiaoqiang.util.XQCryptoUtil") + local misc = XQSysUtil.getMiscHardwareInfo() + local isrecovery = misc.recovery == 1 and true or false + local params = {} + if isrecovery then + local configs = XQSysUtil.getNvramConfigs() + params = { + {"deviceID", ""}, + {"rom", configs.rom_ver}, + {"hardware", XQSysUtil.getHardware()}, + {"cfe", configs.uboot}, + {"linux", configs.linux}, + {"ramfs", configs.ramfs}, + {"sqafs", configs.sqafs}, + {"rootfs", configs.rootfs}, + {"channel", configs.rom_channel}, + {"serialNumber", XQFunction.nvramGet("SN", "")} + } + else + params = { + {"deviceID", getDeviceId()}, + {"rom", XQSysUtil.getRomVersion()}, + {"hardware", XQSysUtil.getHardware()}, + {"cfe", XQSysUtil.getCFEVersion()}, + {"linux", XQSysUtil.getKernelVersion()}, + {"ramfs", XQSysUtil.getRamFsVersion()}, + {"sqafs", XQSysUtil.getSqaFsVersion()}, + {"rootfs", XQSysUtil.getRootFsVersion()}, + {"channel", XQSysUtil.getChannel()}, + {"serialNumber", XQFunction.thrift_to_mqtt_get_sn()} + } + end + local query = {} + table.foreach(params, function(k, v) query[v[1]] = v[2] end) + local queryString = LuciProtocol.urlencode_params(query) + local subUrl = (isrecovery and XIAOQIANG_RECOVERY_UPGRADE or XIAOQIANG_UPGRADE).."?"..queryString + local requestUrl = cryptUrl(XIAOQIANG_SERVER, subUrl, params, DEFAULT_TOKEN) + local response = XQHttpUtil.httpGetRequest(requestUrl) + if response.code ~= 200 then + return false + end + local resultjson = LuciJson.decode(response.res) + if not resultjson then + return false + end + if tonumber(resultjson["code"]) == 0 then + local result = {} + if resultjson.data and resultjson.data.link then + local changeLog = XQFunction.parseEnter2br(luci.util.trim(resultjson.data.description)) + local weight = tonumber(resultjson.data.weight) + result["needUpdate"] = 1 + result["downloadUrl"] = resultjson.data.link + result["fullHash"] = resultjson.data.hash + result["fileSize"] = resultjson.data.size + result["version"] = resultjson.data.toVersion + result["weight"] = weight or 1 + result["changelogUrl"] = resultjson.data.changelogUrl + result["changeLog"] = changeLog + else + result["needUpdate"] = 0 + result["version"] = XQSysUtil.getRomVersion() + result["changeLog"] = XQFunction.parseEnter2br(XQSysUtil.getChangeLog()) + end + return result + else + return false + end +end + +function generateSignature(method,url,params,security) + local XQCryptoUtil = require("xiaoqiang.util.XQCryptoUtil") + local str = "" + if params and #params > 0 then + table.sort(params, function(a, b) return a[1] < b[1] end) + table.foreach(params, function(k, v) str = str..v[1].."="..v[2].."&" end) + end + str = str..security + if not XQFunction.isStrNil(url) then + str = url.."&"..str + end + if not XQFunction.isStrNil(method) then + str = string.upper(method).."&"..str + end + return XQCryptoUtil.hash4SHA1(str) +end + +function doRequest(method,url,param,uuid) + local LuciJson = require("json") + local XQCrypto = require("xqcrypto") + local LuciUtil = require("luci.util") + local XQLog = require("xiaoqiang.XQLog") + local XQCryptoUtil = require("xiaoqiang.util.XQCryptoUtil") + local passport = getPassport(uuid) + method = string.upper(method) + if not passport then + XQLog.log(3,"XQRequest: Passport missing "..url) + return false + end + local nonce = XQCrypto.generateNonce() + local cookies = {} + local encrypt = {} + local plain = {} + local params = {} + cookies["userId"] = passport.uuid + cookies["serviceToken"] = passport.stoken + local sessionSecurity = XQCrypto.generateSessionSecurity(nonce,passport.ssecurity) + local paramStr = "" + if param and type(param) == "table" then + table.sort(param, function(a, b) return a[1] < b[1] end) + for _,item in ipairs(param) do + table.insert(plain,item) + end + else + param = {} + end + local rc4HashPre = generateSignature(method,url,plain,sessionSecurity) + table.insert(param,{"rc4_hash__",rc4HashPre}) + table.sort(param, function(a, b) return a[1] < b[1] end) + for _,item in ipairs(param) do + paramStr = paramStr..item[2]..";;" + end + local paramEncStr = XQCrypto.encryptParams(sessionSecurity,string.sub(paramStr,1,-3)) + local paramsEnc = LuciUtil.split(paramEncStr,";;") + if paramsEnc and #paramsEnc > 0 then + for i,item in ipairs(param) do + table.insert(encrypt,{item[1],paramsEnc[i]}) + table.insert(params,{item[1],paramsEnc[i]}) + end + end + local rc4Hash = "" + local signature = generateSignature(method,url,encrypt,sessionSecurity) + for _,item in ipairs(encrypt) do + if item[1] == "rc4_hash__" then + rc4Hash = item[2] + end + end + table.insert(params,{"signature",signature}) + table.insert(params,{"_nonce",nonce}) + table.insert(params,{"rc4_hash__",rc4Hash}) + local query = {} + table.foreach(params, function(k, v) query[v[1]] = v[2] end) + local queryString = LuciProtocol.xq_urlencode_params(query) + local response + if method == "GET" then + response = XQHttpUtil.httpGetRequest(XIAOQIANG_SERVER..url, queryString, cookies) + elseif method == "POST" then + response = XQHttpUtil.httpPostRequest(XIAOQIANG_SERVER..url, queryString, cookies) + end + if response.code == 200 then + local jsonResult = XQCrypto.decryptResult(sessionSecurity,response.res) + if not XQFunction.isStrNil(jsonResult) then + jsonResult = string.gsub(jsonResult,"u201c","\"") + jsonResult = string.gsub(jsonResult,"u201d","\"") + XQLog.log(7,"XQRequest succeed:"..jsonResult) + return LuciJson.decode(jsonResult) + end + elseif response.code == 401 then + XQLog.log(3,"XQRequest 401:"..XIAOQIANG_SERVER..url,"QueryString:"..queryString,params) + return {code = 401} + end + XQLog.log(3,"XQRequest Failed:"..XIAOQIANG_SERVER..url,"QueryString:"..queryString,params) + return false +end + +function routerRegister(uuid) + local XQSysUtil = require("xiaoqiang.util.XQSysUtil") + local param = {} + local d = XQFunction.thrift_to_mqtt_identify_device() + if XQFunction.isStrNil(d) then + return false + end + local deviceName = XQSysUtil.getRouterName() + table.insert(param,{"d",d}) + table.insert(param,{"deviceName",deviceName}) + local result = doRequest("POST",XIAOQIANG_REGISTER,param,uuid) + return result +end + +function promoteAccount(uuid,account) + local param = {} + local deviceID = getDeviceId() + if XQFunction.isStrNil(deviceID) or XQFunction.isStrNil(account) then + return false + end + table.insert(param,{"account",account}) + table.insert(param,{"deviceID",deviceID}) + local result = doRequest("POST",XIAOQIANG_PROMOTE,param,uuid) + return result +end + +function dismissAccount(uuid,account) + local param = {} + local deviceID = getDeviceId() + if XQFunction.isStrNil(deviceID) or XQFunction.isStrNil(account) then + return false + end + table.insert(param,{"account",account}) + table.insert(param,{"deviceID",deviceID}) + local result = doRequest("POST",XIAOQIANG_DISMISS,param,uuid) + return result +end + +function getAdminList(uuid) + local param = {} + local deviceID = getDeviceId() + if XQFunction.isStrNil(deviceID) then + return false + end + table.insert(param,{"deviceID",deviceID}) + local result = doRequest("GET",XIAOQIANG_ADMINLIST,param,uuid) + return result +end + +function getUserDeviceList(uuid) + local result = doRequest("GET",XIAOQIANG_DEVICELIST,nil,uuid) + return result +end + +function pluginEnable(uuid,pluginId) + local param = {} + local deviceID = getDeviceId() + if XQFunction.isStrNil(deviceID) or XQFunction.isStrNil(pluginId) then + return false + end + table.insert(param,{"deviceID",deviceID}) + table.insert(param,{"pluginID",pluginId}) + local result = doRequest("POST",XIAOQIANG_PLUGIN_ENABLE,param,uuid) + return result +end + +function pluginDisable(uuid,pluginId) + local param = {} + local deviceID = getDeviceId() + if XQFunction.isStrNil(deviceID) or XQFunction.isStrNil(pluginId) then + return false + end + table.insert(param,{"deviceID",deviceID}) + table.insert(param,{"pluginID",pluginId}) + local result = doRequest("POST",XIAOQIANG_PLUGIN_DISABLE,param,uuid) + return result +end + +function pluginList(uuid) + local XQSysUtil = require("xiaoqiang.util.XQSysUtil") + local param = {} + local deviceID = getDeviceId() + local romVersion = XQSysUtil.getRomVersion() + local hardwareNum = XQSysUtil.getHardware() + if XQFunction.isStrNil(deviceID) then + return false + end + table.insert(param,{"deviceID",deviceID}) + table.insert(param,{"rom",romVersion}) + table.insert(param,{"hardware",hardwareNum}) + local result = doRequest("GET",XIAOQIANG_PLUGIN_LIST,param,uuid) + return result +end + +function pluginDetail(uuid,pluginId) + local param = {} + local deviceID = getDeviceId() + if XQFunction.isStrNil(deviceID) or XQFunction.isStrNil(pluginId) then + return false + end + table.insert(param,{"pluginID",pluginId}) + local result = doRequest("GET",XIAOQIANG_PLUGIN_DETAIL,param,uuid) + return result +end + +function updateDeviceName(uuid,deviceName) + local param = {} + local deviceID = getDeviceId() + if XQFunction.isStrNil(deviceID) or XQFunction.isStrNil(deviceName) then + return false + end + table.insert(param,{"deviceID",deviceID}) + table.insert(param,{"deviceName",deviceName}) + local result = doRequest("POST",XIAOQIANG_UPDATE_DEVICENAME,param,uuid) + return result +end + diff --git a/Mi_Lua/xiaoqiang/util/XQQoSUtil.lua b/Mi_Lua/xiaoqiang/util/XQQoSUtil.lua new file mode 100644 index 0000000..c51419c --- /dev/null +++ b/Mi_Lua/xiaoqiang/util/XQQoSUtil.lua @@ -0,0 +1,314 @@ +module ("xiaoqiang.util.XQQoSUtil", package.seeall) + +local cursor = require("luci.model.uci").cursor() + +local XQConfigs = require("xiaoqiang.common.XQConfigs") +local XQFunction = require("xiaoqiang.common.XQFunction") + +function _application() + local cursor = luci.model.uci.cursor() + local config = cursor:get_all("app-tc","config") + local xunlei = cursor:get_all("app-tc","xunlei") + local kuaipan = cursor:get_all("app-tc","kuaipan") + local application = {} + if config then + application.enable = config.enable + end + if xunlei then + application.xunlei = xunlei + end + if kuaipan then + application.kuaipan = kuaipan + end + return application +end + +function _set(section, option, value) + cursor:set("app-tc", section, option, value) +end + +function _apply() + cursor:save("app-tc") + cursor:commit("app-tc") +end + +function _appSpeedlimit(app, maxdownload, maxupload) + if maxdownload then + _set(app, "max_download_speed", tostring(maxdownload)) + end + if maxupload then + _set(app, "max_upload_speed", tostring(maxupload)) + end + _apply() +end + +function appSpeedlimitSwitch(enable) + local cmd = enable and XQConfigs.QOS_APPSL_ENABLE or XQConfigs.QOS_APPSL_DISABLE + local value = enable and "1" or "0" + _set("config", "enable", value) + _apply() + return (os.execute(cmd) == 0) +end + +function appInfo() + local json = require("json") + local LuciUtil = require("luci.util") + local info = {} + local xunlei = {} + local kuaipan = {} + local application = _application() + + local xlcspeed = XQFunction.thrift_tunnel_to_datacenter([[{"api":45,"appCode":1}]]) + local kpcspeed = XQFunction.thrift_tunnel_to_datacenter([[{"api":45,"appCode":0}]]) + + if xlcspeed and xlcspeed.code == 0 then + xunlei.download = tonumber(xlcspeed.downloadSpeed) + xunlei.upload = tonumber(xlcspeed.uploadSpeed) + else + xunlei.download = 0 + xunlei.upload = 0 + end + if kpcspeed and kpcspeed.code == 0 then + kuaipan.download = tonumber(kpcspeed.downloadSpeed) + kuaipan.upload = tonumber(kpcspeed.uploadSpeed) + else + kuaipan.download = 0 + kuaipan.upload = 0 + end + info.enable = application.enable + xunlei.enable = application.xunlei.enable + xunlei.maxdownload = tonumber(application.xunlei.max_download_speed) + xunlei.maxupload = tonumber(application.xunlei.max_upload_speed) + + kuaipan.enable = application.kuaipan.enable + kuaipan.maxdownload = tonumber(application.kuaipan.max_download_speed) + kuaipan.maxupload = tonumber(application.kuaipan.max_upload_speed) + info.xunlei = xunlei + info.kuaipan = kuaipan + return info +end + +function setXunlei(maxdownload, maxupload) + _appSpeedlimit("xunlei", maxdownload, maxupload) +end + +function setKuaipan(maxdownload, maxupload) + _appSpeedlimit("kuaipan", maxdownload, maxupload) +end + +function reload() + os.execute(XQConfigs.QOS_APPSL_RELOAD) +end + +-- +-- smart qos +-- +-- return KB +function _bitFormat(bits) + if XQFunction.isStrNil(bits) then + return 0 + end + if type(bits) == "number" then + return tonumber(string.format("%0.2f", bits/8000)) + end + if bits:match("Gbit") then + return tonumber(bits:match("(%S+)Gbit"))*125000 + elseif bits:match("Mbit") then + return tonumber(bits:match("(%S+)Mbit"))*125 + elseif bits:match("Kbit") then + return tonumber(string.format("%0.2f",tonumber(bits:match("(%S+)Kbit"))/8)) + elseif bits:match("bit") then + return tonumber(string.format("%0.2f",tonumber(bits:match("(%S+)bit"))/8000)) + else + return 0 + end +end + +function _weightHelper(level) + if level == 1 then + return 0.25 + elseif level == 2 then + return 0.5 + elseif level == 3 then + return 0.75 + else + return 0.1 + end +end + +-- 0/1/2/3 unset/low/middel/high +function _levelHelper(weight) + if weight == 0 then + return 2 + elseif weight > 0 and weight <= 0.25 then + return 1 + elseif weight > 0.25 and weight <= 0.5 then + return 2 + elseif weight > 0.5 then + return 3 + end + return 0 +end + +function qosSwitch(on) + if on then + return os.execute("/etc/init.d/miqos on") == 0 + else + return os.execute("/etc/init.d/miqos off") == 0 + end +end + +function qosModeSwitch(auto) + if auto then + return os.execute("/etc/init.d/miqos auto on") == 0 + else + return os.execute("/etc/init.d/miqos auto off") == 0 + end +end + +function qosRestart() + return os.execute("/etc/init.d/miqos restart") +end + +-- on : 0/1 close/open +-- mode : 0/1 auto/manual +function qosStatus() + local uci = require("luci.model.uci").cursor() + local status = {} + if os.execute("/etc/init.d/miqos status") == 0 then + status["on"] = 1 + else + status["on"] = 0 + end + local auto = uci:get("miqos", "settings", "qos_auto") + if auto and tonumber(auto) == 1 then + status["mode"] = 0 + else + status["mode"] = 1 + end + return status +end + +-- M bits/s +function qosBand() + local MiQos = require("sysapi.miqos") + local result = { + ["download"] = 0, + ["upload"] = 0 + } + local band = MiQos.cmd("show_band") + if band and band.status == 0 and band.data then + result.download = tonumber(string.format("%0.2f", band.data.downlink/1000)) + result.upload = tonumber(string.format("%0.2f", band.data.uplink/1000)) + end + return result +end + +-- M bits/s +function setQosBand(upload, download) + local MiQos = require("sysapi.miqos") + if upload and download then + local upload = tostring(math.floor(1000*upload)) + local download = tostring(math.floor(1000*download)) + local setband = MiQos.cmd(string.format("change_band %s %s", upload, download)) + if setband and setband.status == 0 then + return true + end + end + return false +end + +function qosList() + local LuciUtil = require("luci.util") + local MiQos = require("sysapi.miqos") + local XQDeviceUtil = require("xiaoqiang.util.XQDeviceUtil") + local result = {} + local devicedict = {} + local devicelist = XQDeviceUtil.getConDevices(true) + local qoslist = MiQos.cmd("show_limit") + local band = qosBand() + if devicelist and type(devicelist) == "table" and #devicelist > 0 then + for _, item in ipairs(devicelist) do + devicedict[item.ip] = item + end + end + if devicedict and qoslist and qoslist.status == 0 and qoslist.data then + for ip, value in pairs(qoslist.data) do + local device = devicedict[ip] + if device then + device = LuciUtil.clone(device, true) + device.ip = ip + local qos = {} + qos["downmax"] = _bitFormat(value.DOWN.max) + qos["downmin"] = _bitFormat(value.DOWN.min) + local dpercent + local level + if band.download > 0 then + dpercent = 100 * (tonumber(value.DOWN.max_per) or 1) + level = _levelHelper(tonumber(value.DOWN.min_per) or 0) + else + level = 2 + dpercent = 100 + end + qos["maxdownper"] = dpercent + qos["upmax"] = _bitFormat(value.UP.max) + qos["upmin"] = _bitFormat(value.UP.min) + local upercent + if band.upload > 0 then + upercent = 100 * (tonumber(value.UP.max_per) or 1) + else + upercent = 100 + end + qos["level"] = level + qos["upmaxper"] = upercent + device["qos"] = qos + table.insert(result, device) + end + end + end + return result +end + +-- maxup/maxdown (0, 1] +-- upweight/downweight (low, middle, high) +function qosOnLimit(mac, maxup, maxdown, upweight, downweight) + local MiQos = require("sysapi.miqos") + if mac and maxup and maxdown and upweight and downweight then + local maxup = tonumber(maxup) + local maxdown = tonumber(maxdown) + if maxup > 1 then + maxup = 1 + elseif maxup <= 0 then + maxup = 0.1 + end + if maxdown > 1 then + maxdown = 1 + elseif maxdown <= 0 then + maxdown = 0.1 + end + local upweight = tostring(_weightHelper(upweight)) + local downweight = tostring(_weightHelper(downweight)) + local mac = XQFunction.macFormat(mac) + local limit = MiQos.cmd(string.format("on_limit %s %s %s %s %s", mac, maxup, maxdown, upweight, downweight)) + if limit and limit.status == 0 then + return true + end + end + return false +end + +-- if mac = nil then clear all limits +function qosOffLimit(mac) + local MiQos = require("sysapi.miqos") + local offlimit + if not XQFunction.isStrNil(mac) then + offlimit = MiQos.cmd(string.format("off_limit %s", XQFunction.macFormat(mac))) + else + offlimit = MiQos.cmd(string.format("off_limit")) + end + if offlimit and offlimit.status == 0 then + return true + else + return false + end +end diff --git a/Mi_Lua/xiaoqiang/util/XQSDKUtil.lua b/Mi_Lua/xiaoqiang/util/XQSDKUtil.lua new file mode 100644 index 0000000..b2bb364 --- /dev/null +++ b/Mi_Lua/xiaoqiang/util/XQSDKUtil.lua @@ -0,0 +1,62 @@ +module ("xiaoqiang.util.XQSDKUtil", package.seeall) + +local XQLog = require("xiaoqiang.XQLog") +local XQPreference = require("xiaoqiang.XQPreference") + +local XQConfigs = require("xiaoqiang.common.XQConfigs") +local XQFunction = require("xiaoqiang.common.XQFunction") +local XQCryptoUtil = require("xiaoqiang.util.XQCryptoUtil") + +CONFIG_MACFILTER = "sdkfilter" + +function _permissionFilter(mac) + local XQDeviceUtil = require("xiaoqiang.util.XQDeviceUtil") + local dhcpDict = XQDeviceUtil.getDHCPDict() + local dhcpInfo = dhcpDict[mac] + if dhcpInfo and dhcpInfo.name and string.lower(dhcpInfo.name):match("^mitv") then + XQPreference.set(_formatMac(mac), "1", CONFIG_MACFILTER) + XQLog.log(6, "SDK filter. Empower mac:"..mac..". MiTV") + return true + else + XQLog.log(6, "SDK filter. Check failed!"..mac..".dhcp info:",dhcpInfo) + return false + end +end + +function _formatMac(mac) + if XQFunction.isStrNil(mac) then + return nil + end + return mac:gsub(":","") +end + +function checkPermission(mac) + if XQFunction.isStrNil(mac) then + return false + else + mac = XQFunction.macFormat(mac) + end + local permission = XQPreference.get(_formatMac(mac), nil, CONFIG_MACFILTER) + if permission then + if permission == "1" then + XQLog.log(6, "SDK filter. mac:"..mac.." OK!") + return true + else + XQLog.log(6, "SDK filter. mac:"..mac.." not allowed") + return false + end + else + return _permissionFilter(mac) + end + return false +end + +function setPermission(mac, permission) + if XQFunction.isStrNil(mac) then + return false + else + mac = XQFunction.macFormat(mac) + end + local permission = permission and "1" or "0" + XQPreference.set(_formatMac(mac), permission, CONFIG_MACFILTER) +end \ No newline at end of file diff --git a/Mi_Lua/xiaoqiang/util/XQSecureUtil.lua b/Mi_Lua/xiaoqiang/util/XQSecureUtil.lua new file mode 100644 index 0000000..7505b01 --- /dev/null +++ b/Mi_Lua/xiaoqiang/util/XQSecureUtil.lua @@ -0,0 +1,346 @@ +module ("xiaoqiang.util.XQSecureUtil", package.seeall) + +require("luci.util") +require("luci.sys") +local bit = require("bit") +local nixio = require "nixio", require "nixio.util" +local fs = require "nixio.fs" + +local XQLog = require("xiaoqiang.XQLog") +local XSSFilter = require("xssFilter").new() +local XQFunction = require("xiaoqiang.common.XQFunction") +local XQPreference = require("xiaoqiang.XQPreference") +local XQCryptoUtil = require("xiaoqiang.util.XQCryptoUtil") + +local PWDKEY = "a2ffa5c9be07488bbb04a3a47d3c5f6a" +local DECCIPERTEXT = "echo -e '%s' | openssl aes-128-cbc -d -K %s -iv '64175472480004614961023454661220' -base64" +local NONCEPATH = "/tmp/luci-nonce" + +function checkid(id) + return not not (id and #id == 40 and id:match("^[a-fA-F0-9]+$")) +end + +function prepare() + fs.mkdir(NONCEPATH, 700) + if not sane() then + error("Security Exception: Nonce path is not sane!") + end +end + +function sane(file) + return luci.sys.process.info("uid") + == fs.stat(file or NONCEPATH, "uid") + and fs.stat(file or NONCEPATH, "modestr") + == (file and "rw-------" or "rwx------") +end + +function readNonce(id) + if not id or not checkid(id) then + return nil + end + if not sane(NONCEPATH .. "/" .. id) then + return nil + end + local blob = fs.readfile(NONCEPATH .. "/" .. id) + local func = loadstring(blob) + setfenv(func, {}) + + local nonceinfo = func() + if type(nonceinfo) ~= "table" then + return nil + end + return nonceinfo +end + +function writeNonce(id, data) + if not sane() then + prepare() + end + if not checkid(id) or type(data) ~= "table" then + return + end + data = luci.util.get_bytecode(data) + local f = nixio.open(NONCEPATH .. "/" .. id, "w", 600) + f:writeall(data) + f:close() +end + +function xssCheck(value) + if XQFunction.isStrNil(value) then + return value + end + if type(value) == "string" then + local cvalue,message = XSSFilter:filter(value) + if cvalue then + return value + else + local XQLog = require("xiaoqiang.XQLog") + XQLog.log(4,"XSS Warning:"..value) + return nil + end + else + return value + end +end + +function generateRedirectKey(type) + local LuciSys = require("luci.sys") + local LuciSauth = require("luci.sauth") + local info = {} + local id = LuciSys.uniqueid(16) + info["type"] = tostring(type) + LuciSauth.write(id,info) + return id +end + +function checkRedirectKey(key) + if XQFunction.isStrNil(key) then + return false + end + local LuciSys = require("luci.sys") + local LuciSauth = require("luci.sauth") + local info = LuciSauth.read(key) + if info and type(info) == "table" then + LuciSauth.kill(key) + local uptime = LuciSys.uptime() + if uptime - info.atime > 10 then + return false + else + return tostring(info.type) + end + end + return false +end + +function ciphertextFormat(ciphertext) + if XQFunction.isStrNil(ciphertext) then + return "" + end + local len = math.ceil(#ciphertext/64) + local str = {} + for i=1, len do + if i ~= len then + table.insert(str, string.sub(ciphertext,1+(i-1)*64,64*i)) + else + table.insert(str, string.sub(ciphertext,1+(i-1)*64,-1)) + end + end + return table.concat(str, "\\n") +end + +function decCiphertext(user, ciphertext) + if XQFunction.isStrNil(ciphertext) then + return nil + end + local password = XQPreference.get(user, "", "account") + local cmd = string.format(DECCIPERTEXT, ciphertextFormat(ciphertext), password) + if os.execute(cmd) == 0 then + return luci.util.trim(luci.util.exec(cmd)) + end +end + +function savePlaintextPwd(user, plaintext) + if XQFunction.isStrNil(user) or XQFunction.isStrNil(plaintext) then + return false + end + local pwd = XQCryptoUtil.sha1(plaintext..PWDKEY) + XQPreference.set(user, pwd, "account") + XQFunction.nvramSet("nv_sys_pwd", pwd) + XQFunction.nvramCommit() + return true +end + +function saveCiphertextPwd(user, ciphertext) + if XQFunction.isStrNil(user) or XQFunction.isStrNil(ciphertext) then + return false + end + local pwd = decCiphertext(user, ciphertext) + if pwd then + XQPreference.set(user, pwd, "account") + XQFunction.nvramSet("nv_sys_pwd", pwd) + XQFunction.nvramCommit() + return true + end + return false +end + +-- only for old pwd +function checkPlaintextPwd(user, plaintext) + if XQFunction.isStrNil(user) or XQFunction.isStrNil(plaintext) then + return false + end + local password = XQPreference.get(user, "", "account") + local cpwd = XQCryptoUtil.sha1(plaintext..PWDKEY) + if password == cpwd then + return true + else + return false + end +end + +function checkUser(user, nonce, encStr) + local password = XQPreference.get(user, nil, "account") + if password and not XQFunction.isStrNil(encStr) and not XQFunction.isStrNil(nonce) then + if XQCryptoUtil.sha1(nonce..password) == encStr then + return true + end + end + return false +end + +--[[ + nonce = type.."+"..deviceId.."+"..time.."+"..random + type [0 web] [1 Android] [2 iOS] [3 Mac] [4 PC] +]]-- +function checkNonce(nonce, mac) + local LuciUtil = require("luci.util") + local LuciSys = require("luci.sys") + local XQCryptoUtil = require("xiaoqiang.util.XQCryptoUtil") + if nonce and mac then + mac = XQFunction.macFormat(mac) + local nonceInfo = LuciUtil.split(nonce, "_") + if #nonceInfo ~= 4 then + XQLog.log(6,"Nonce check failed!: Illegal" .. nonce .. " remote MAC address:" .. mac) + return false + end + local dtype = tonumber(nonceInfo[1]) + local deviceId = tostring(nonceInfo[2]) + local time = tonumber(nonceInfo[3]) + if dtype and deviceId then + local key = XQCryptoUtil.sha1(dtype..deviceId) + if dtype > 4 then + XQLog.log(6,"Nonce check failed! Type error:" .. nonce .. " remote MAC address:" .. mac) + return false + end + local cache = readNonce(key) + if cache and type(cache) == "table" then + if time > tonumber(cache.mark) then + if mac ~= cache.mac then + XQLog.log(6,"Mac address changed: " .. cache.mac .. " --> " .. mac, cache, nonce) + end + cache["mark"] = tostring(time) + writeNonce(key,cache) + return true + else + XQLog.log(6,"Nonce check failed!: Not match" .. nonce .. " remote MAC address:" .. mac, cache) + end + else + cache = {} + cache["mark"] = tostring(time) + cache["mac"] = mac + writeNonce(key,cache) + return true + end + end + end + return false +end + +local SID = "xiaoqiang-web" + +function passportLoginUrl() + local LuciProtocol = require("luci.http.protocol") + local XQConfigs = require("xiaoqiang.common.XQConfigs") + local XQCryptoUtil = require("xiaoqiang.util.XQCryptoUtil") + local url + local followup = "http://miwifi.com/cgi-bin/luci/web/xmaccount" + local tobeSign = "followup="..followup + local sign = XQCryptoUtil.binaryBase64Enc(XQCryptoUtil.sha1Binary(tobeSign)) + if XQConfigs.SERVER_CONFIG == 0 then + url = XQConfigs.PASSPORT_CONFIG_ONLINE_URL.. + "?callback="..LuciProtocol.urlencode(XQConfigs.XQ_SERVER_ONLINE_STS_URL.."?sign="..sign.."&followup="..followup).. + "&sid="..SID + elseif XQConfigs.SERVER_CONFIG == 1 then + url = XQConfigs.PASSPORT_CONFIG_PREVIEW_URL.. + "?callback="..LuciProtocol.urlencode(XQConfigs.XQ_SERVER_STAGING_STS_URL.."?sign="..sign.."&followup="..followup).. + "&sid="..SID + end + return url +end + +function passportLogoutUrl() + local XQSysUtil = require("xiaoqiang.util.XQSysUtil") + local LuciProtocol = require("luci.http.protocol") + local XQConfigs = require("xiaoqiang.common.XQConfigs") + local url + local uuid = XQSysUtil.getPassportBindInfo() + if XQFunction.isStrNil(uuid) then + return "" + end + local callback = "http://miwifi.com/cgi-bin/luci/web/home" + if XQConfigs.SERVER_CONFIG == 0 then + url = XQConfigs.PASSPORT_LOGOUT_ONLINE_URL.. + "?callback="..LuciProtocol.urlencode(callback).. + "&sid="..SID.."&userId="..uuid + elseif XQConfigs.SERVER_CONFIG == 1 then + url = XQConfigs.PASSPORT_LOGOUT_PREVIEW_URL.. + "?callback="..LuciProtocol.urlencode(callback).. + "&sid="..SID.."&userId="..uuid + end + return url +end + +-- check wifi password +function _charMode(char) + if char >= 48 and char <= 57 then -- 数字 + return 1 + elseif char >= 65 and char <= 90 then -- 大写 + return 2 + elseif char >= 97 and char <= 122 then -- 小写 + return 4 + else -- 特殊字符 + return 8 + end +end + +function _bitTotal(num) + local modes = 0 + for i=1,4 do + if bit.band(num, 1) == 1 then + modes = modes + 1 + end + num = bit.rshift(num, 1) + end + return modes +end + +function checkStrong(pwd) + if XQFunction.isStrNil(pwd) or (pwd and string.len(pwd) < 8) then + return 0 + end + local modes = 0 + for i=1,string.len(pwd) do + local sss = _charMode(string.byte(pwd,i)) + modes = bit.bor(modes, sss) + end + return _bitTotal(modes) +end + +-- check url +KEY_WORDS = { + "'", + ";", + "nvram", + "dropbear", + "bdata" +} + +function _keyWordsFilter(value) + if XQFunction.isStrNil(value) then + return true + else + value = string.lower(value) + end + for _, keyword in ipairs(KEY_WORDS) do + if value:match(keyword) then + local XQLog = require("xiaoqiang.XQLog") + XQLog.log(6,"Keyword Warning:"..value) + return false + end + end + return true +end + +function cmdSafeCheck(url) + return _keyWordsFilter(url) +end diff --git a/Mi_Lua/xiaoqiang/util/XQSysUtil.lua b/Mi_Lua/xiaoqiang/util/XQSysUtil.lua new file mode 100644 index 0000000..b81eff2 --- /dev/null +++ b/Mi_Lua/xiaoqiang/util/XQSysUtil.lua @@ -0,0 +1,809 @@ +module ("xiaoqiang.util.XQSysUtil", package.seeall) + +local XQFunction = require("xiaoqiang.common.XQFunction") +local XQConfigs = require("xiaoqiang.common.XQConfigs") + +function getInitInfo() + local initted = require("xiaoqiang.XQPreference").get(XQConfigs.PREF_IS_INITED) + if initted then + return true + else + return false + end +end + +function setInited() + require("xiaoqiang.XQPreference").set(XQConfigs.PREF_IS_INITED, "YES") + local LuciUtil = require("luci.util") + LuciUtil.exec("/usr/sbin/sysapi webinitrdr set off") + return true +end + +function setSPwd() + local LuciUtil = require("luci.util") + local genpwd = LuciUtil.exec("mkxqimage -I") + if genpwd then + local LuciSys = require("luci.sys") + genpwd = LuciUtil.trim(genpwd) + LuciSys.user.setpasswd("root", genpwd) + end +end + +function getChangeLog() + local LuciFs = require("luci.fs") + local LuciUtil = require("luci.util") + if LuciFs.access(XQConfigs.XQ_CHANGELOG_FILEPATH) then + return LuciUtil.exec("cat "..XQConfigs.XQ_CHANGELOG_FILEPATH) + end + return "" +end + +function getMiscHardwareInfo() + local uci = require("luci.model.uci").cursor() + local result = {} + result["bbs"] = tostring(uci:get("misc", "hardware", "bbs")) + result["verify"] = tostring(uci:get("misc", "hardware", "verify")) + result["gpio"] = tonumber(uci:get("misc", "hardware", "gpio")) == 1 and 1 or 0 + result["recovery"] = tonumber(uci:get("misc", "hardware", "recovery")) == 1 and 1 or 0 + result["flashpermission"] = tonumber(uci:get("misc", "hardware", "flash_per")) == 1 and 1 or 0 + return result +end + +function getPassportBindInfo() + local XQPreference = require("xiaoqiang.XQPreference") + local initted = XQPreference.get(XQConfigs.PREF_IS_PASSPORT_BOUND) + local bindUUID = XQPreference.get(XQConfigs.PREF_PASSPORT_BOUND_UUID, "") + if not XQFunction.isStrNil(initted) and initted == "YES" and not XQFunction.isStrNil(bindUUID) then + return bindUUID + else + return false + end +end + +function setPassportBound(bind,uuid) + local XQPreference = require("xiaoqiang.XQPreference") + local XQDBUtil = require("xiaoqiang.util.XQDBUtil") + if bind then + if not XQFunction.isStrNil(uuid) then + XQPreference.set(XQConfigs.PREF_PASSPORT_BOUND_UUID,uuid) + end + XQPreference.set(XQConfigs.PREF_IS_PASSPORT_BOUND, "YES") + XQPreference.set(XQConfigs.PREF_TIMESTAMP, "0") + else + if not XQFunction.isStrNil(uuid) then + XQPreference.set(XQConfigs.PREF_PASSPORT_BOUND_UUID,"") + end + XQPreference.set(XQConfigs.PREF_IS_PASSPORT_BOUND, "NO") + XQPreference.set(XQConfigs.PREF_BOUND_USERINFO, "") + end + return true +end + +function getSysUptime() + local LuciUtil = require("luci.util") + local catUptime = "cat /proc/uptime" + local data = LuciUtil.exec(catUptime) + if data == nil then + return 0 + else + local t1,t2 = data:match("^(%S+) (%S+)") + return LuciUtil.trim(t1) + end +end + +function getConfigInfo() + local LuciUtil = require("luci.util") + return LuciUtil.exec("cat /etc/config/*") +end + +function getRouterName() + return require("xiaoqiang.XQPreference").get(XQConfigs.PREF_ROUTER_NAME, "") +end + +function setRouterName(routerName) + local XQNetUtil = require("xiaoqiang.util.XQNetUtil") + local XQLog = require("xiaoqiang.XQLog") + local oldRouterName = getRouterName() + if routerName and oldRouterName ~= routerName then + require("xiaoqiang.XQPreference").set(XQConfigs.PREF_ROUTER_NAME, routerName) + setRouterNamePending('1') + return true + else + return false + end +end + +function getRouterNamePending() + return require("xiaoqiang.XQPreference").get(XQConfigs.PREF_ROUTER_NAME_PENDING, '0') +end + +function setRouterNamePending(pending) + return require("xiaoqiang.XQPreference").set(XQConfigs.PREF_ROUTER_NAME_PENDING, pending) +end + +function getBindUUID() + return require("xiaoqiang.XQPreference").get(XQConfigs.PREF_PASSPORT_BOUND_UUID, "") +end + +function setBindUUID(uuid) + return require("xiaoqiang.XQPreference").set(XQConfigs.PREF_PASSPORT_BOUND_UUID, uuid) +end + +function setBindUserInfo(userInfo) + local LuciJson = require("json") + local XQPreference = require("xiaoqiang.XQPreference") + local XQConfigs = require("xiaoqiang.common.XQConfigs") + local XQCryptoUtil = require("xiaoqiang.util.XQCryptoUtil") + if userInfo and type(userInfo) == "table" then + local userInfoStr = LuciJson.encode(userInfo) + XQPreference.set(XQConfigs.PREF_BOUND_USERINFO,XQCryptoUtil.binaryBase64Enc(userInfoStr)) + end +end + +function getBindUserInfo() + local LuciJson = require("json") + local XQPreference = require("xiaoqiang.XQPreference") + local XQConfigs = require("xiaoqiang.common.XQConfigs") + local XQCryptoUtil = require("xiaoqiang.util.XQCryptoUtil") + local infoStr = XQPreference.get(XQConfigs.PREF_BOUND_USERINFO,nil) + if infoStr and infoStr ~= "" then + infoStr = XQCryptoUtil.binaryBase64Dec(infoStr) + if infoStr then + return LuciJson.decode(infoStr) + end + else + return nil + end +end + +function getRomVersion() + local LuciUtil = require("luci.util") + local romVersion = LuciUtil.exec(XQConfigs.XQ_ROM_VERSION) + if XQFunction.isStrNil(romVersion) then + romVersion = "" + end + return LuciUtil.trim(romVersion) +end + +function getChannel() + local LuciUtil = require("luci.util") + local channel = LuciUtil.exec(XQConfigs.XQ_CHANNEL) + if XQFunction.isStrNil(channel) then + channel = "" + end + return LuciUtil.trim(channel) +end + +-- From GPIO +function getHardwareVersion() + local h = XQFunction.getGpioValue(14) + local m = XQFunction.getGpioValue(13) + local l = XQFunction.getGpioValue(12) + local offset = h * 4 + m * 2 + l + local char = string.char(65+offset) + return "Ver."..char +end + +function getHardwareGPIO() + local LuciUtil = require("luci.util") + local hardware = LuciUtil.exec(XQConfigs.XQ_HARDWARE) + if XQFunction.isStrNil(hardware) then + hardware = "" + else + hardware = LuciUtil.trim(hardware) + end + local misc = getMiscHardwareInfo() + if misc.gpio == 1 then + return getHardwareVersion() + end + return hardware +end + +function getHardware() + local LuciUtil = require("luci.util") + local hardware = LuciUtil.exec(XQConfigs.XQ_HARDWARE) + if XQFunction.isStrNil(hardware) then + hardware = "" + else + hardware = LuciUtil.trim(hardware) + end + return hardware +end + +function getCFEVersion() + local LuciUtil = require("luci.util") + local cfe = LuciUtil.exec(XQConfigs.XQ_CFE_VERSION) + if XQFunction.isStrNil(cfe) then + cfe = "" + end + return LuciUtil.trim(cfe) +end + +function getKernelVersion() + local LuciUtil = require("luci.util") + local kernel = LuciUtil.exec(XQConfigs.XQ_KERNEL_VERSION) + if XQFunction.isStrNil(kernel) then + kernel = "" + end + return LuciUtil.trim(kernel) +end + +function getRamFsVersion() + local LuciUtil = require("luci.util") + local ramFs = LuciUtil.exec(XQConfigs.XQ_RAMFS_VERSION) + if XQFunction.isStrNil(ramFs) then + ramFs = "" + end + return LuciUtil.trim(ramFs) +end + +function getSqaFsVersion() + local LuciUtil = require("luci.util") + local sqaFs = LuciUtil.exec(XQConfigs.XQ_SQAFS_VERSION) + if XQFunction.isStrNil(sqaFs) then + sqaFs = "" + end + return LuciUtil.trim(sqaFs) +end + +function getRootFsVersion() + local LuciUtil = require("luci.util") + local rootFs = LuciUtil.exec(XQConfigs.XQ_ROOTFS_VERSION) + if XQFunction.isStrNil(rootFs) then + rootFs = "" + end + return LuciUtil.trim(rootFs) +end + +function getLangList() + local LuciUtil = require("luci.util") + local LuciConfig = require("luci.config") + local langs = {} + for k, v in LuciUtil.kspairs(LuciConfig.languages) do + if type(v)=="string" and k:sub(1, 1) ~= "." then + local lang = {} + lang['lang'] = k + lang['name'] = v + table.insert(langs,lang) + end + end + return langs +end + +function getLang() + local LuciConfig = require("luci.config") + return LuciConfig.main.lang +end + +function setLang(lang) + local LuciUtil = require("luci.util") + local LuciUci = require("luci.model.uci") + local LuciConfig = require("luci.config") + for k, v in LuciUtil.kspairs(LuciConfig.languages) do + if type(v) == "string" and k:sub(1, 1) ~= "." then + if lang == k or lang == "auto" then + local cursor = LuciUci.cursor() + if lang=="auto" then + cursor:set("luci", "main" , "lang" , "auto") + else + cursor:set("luci", "main" , "lang" , k) + end + cursor:commit("luci") + cursor:save("luci") + return true + end + end + end + return false +end + +function setSysPasswordDefault() + local LuciSys = require("luci.sys") + local XQSecureUtil = require("xiaoqiang.util.XQSecureUtil") + XQSecureUtil.savePlaintextPwd("admin", "admin") +end + +function checkSysPassword(oldPassword) + local LuciSys = require("luci.sys") + return LuciSys.user.checkpasswd("root", oldPassword) +end + +function setSysPassword(newPassword) + local LuciSys = require("luci.sys") + local XQSecureUtil = require("xiaoqiang.util.XQSecureUtil") + check = LuciSys.user.setpasswd("root", newPassword) + XQSecureUtil.savePlaintextPwd("admin", newPassword) + if check == 0 then + return true + else + local LuciUtil = require("luci.util") + LuciUtil.exec("rm /etc/passwd+") + end + return false +end + +function cutImage(filePath) + if not filePath then + return false + end + local code = os.execute(XQConfigs.XQ_CUT_IMAGE..filePath) + if 0 == code or 127 == code then + return true + else + return false + end +end + +function verifyImage(filePath) + if not filePath then + return false + end + if 0 == os.execute(XQConfigs.XQ_VERIFY_IMAGE..filePath) then + return true + else + return false + end +end + +function getSysInfo() + local LuciSys = require("luci.sys") + local LuciUtil = require("luci.util") + local sysInfo = {} + local processor = LuciUtil.execl("cat /proc/cpuinfo | grep processor") + local platform, model, memtotal, memcached, membuffers, memfree, bogomips = LuciSys.sysinfo() + if #processor > 0 then + sysInfo["core"] = #processor + else + sysInfo["core"] = 1 + end + local chippkg = LuciUtil.exec(XQConfigs.GET_CPU_CHIPPKG) + if chippkg then + chippkg = tonumber(LuciUtil.trim(chippkg)) + if chippkg == 0 then + sysInfo["hz"] = "1GHz" + else + sysInfo["hz"] = "800MHz" + end + else + sysInfo["hz"] = XQFunction.hzFormat(tonumber(bogomips)*500000) + end + sysInfo["system"] = platform + sysInfo["memTotal"] = string.format("%0.2f M",memtotal/1024) + sysInfo["memFree"] = string.format("%0.2f M",memfree/1024) + return sysInfo +end + +function setMacFilter(mac,lan,wan,admin,pridisk) + if not XQFunction.isStrNil(mac) then + local cmd = "/usr/sbin/sysapi macfilter set mac="..mac + if wan then + cmd = cmd.." wan="..(wan == "1" and "yes" or "no") + end + if lan then + cmd = cmd.." lan="..(lan == "1" and "yes" or "no") + end + if admin then + cmd = cmd.." admin="..(admin == "1" and "yes" or "no") + end + if pridisk then + cmd = cmd.." pridisk="..(pridisk == "1" and "yes" or "no") + end + if os.execute(cmd..";".."/usr/sbin/sysapi macfilter commit") == 0 then + return true + end + end + return false +end + +function getDiskSpace() + local LuciUtil = require("luci.util") + local disk = LuciUtil.exec(XQConfigs.DISK_SPACE) + if disk and tonumber(LuciUtil.trim(disk)) then + disk = tonumber(LuciUtil.trim(disk)) + return XQFunction.byteFormat(disk*1024) + else + return "Cannot find userdisk" + end +end + +function getAvailableMemery() + local LuciUtil = require("luci.util") + local memery = LuciUtil.exec(XQConfigs.AVAILABLE_MEMERY) + if memery and tonumber(LuciUtil.trim(memery)) then + return tonumber(LuciUtil.trim(memery)) + else + return false + end +end + +function getAvailableDisk() + local LuciUtil = require("luci.util") + local disk = LuciUtil.exec(XQConfigs.AVAILABLE_DISK) + if disk and tonumber(LuciUtil.trim(disk)) then + return tonumber(LuciUtil.trim(disk)) + else + return false + end +end + +function checkDiskSpace(byte) + local disk = getAvailableDisk() + if disk then + if disk - byte/1024 > 10240 then + return true + end + end + return false +end + +function checkTmpSpace(byte) + local tmp = getAvailableMemery() + if tmp then + if tmp - byte/1024 > 10240 then + return true + end + end + return false +end + +function updateUpgradeStatus(status) + local status = tostring(status) + os.execute("echo "..status.." > "..XQConfigs.UPGRADE_LOCK_FILE) +end + +function getUpgradeStatus() + local LuciUtil = require("luci.util") + local status = tonumber(LuciUtil.exec(XQConfigs.UPGRADE_STATUS)) + if status then + return status + else + return 0 + end +end + +function checkBeenUpgraded() + local LuciUtil = require("luci.util") + local otaFlag = tonumber(LuciUtil.trim(LuciUtil.exec("nvram get flag_ota_reboot"))) + if otaFlag == 1 then + return true + else + return false + end +end + +--[[ + 0 : 没有flash + 1 : 正在执行flash + 2 : flash成功 需要重启 + 3 : flash失败 +]]-- +function getFlashStatus() + local LuciFs = require("luci.fs") + if checkBeenUpgraded() then + return 2 + end + local check = os.execute(XQConfigs.FLASH_EXECUTION_CHECK) + if check ~= 0 then + return 1 + end + if not LuciFs.access(XQConfigs.FLASH_PID_TMP) then + return 0 + else + return 3 + end +end + +function checkExecStatus(checkCmd) + local LuciUtil = require("luci.util") + local check = LuciUtil.exec(checkCmd) + if check then + check = tonumber(LuciUtil.trim(check)) + if check > 0 then + return 1 + end + end + return 0 +end + +--[[ + 0 : 没有upgrade + 1 : 检查升级 + 2 : 检查tmp 磁盘是否有空间下载 + 3 : 下载升级包 + 4 : 检测升级包 + 5 : 刷写升级包 + 6 : 没有检测到更新 + 7 : 没有磁盘空间 + 8 : 下载失败 + 9 : 升级包校验失败 + 10 : 刷写失败 + 11 : 升级成功 + 12 : 手动升级在刷写升级包 +]]-- +function checkUpgradeStatus() + local LuciFs = require("luci.fs") + if checkBeenUpgraded() then + return 11 + end + local status = getUpgradeStatus() + if checkExecStatus(XQConfigs.CRONTAB_ROM_CHECK) == 1 then + if status == 0 then + return 1 + else + return status + end + end + local checkFlash = os.execute(XQConfigs.FLASH_EXECUTION_CHECK) + if checkFlash ~= 0 then + if checkExecStatus(XQConfigs.CROM_FLASH_CHECK) == 1 then + return 12 + else + return 5 + end + end + local flashStatus = getFlashStatus() + local execute = LuciFs.access(XQConfigs.CRONTAB_PID_TMP) + if execute then + if status == 0 then + if flashStatus == 2 then + return 11 + elseif flashStatus == 3 then + return 10 + end + end + return status + else + if flashStatus == 2 then + return 11 + elseif flashStatus == 3 then + return 10 + end + end + return 0 +end + +function isUpgrading() + local status = checkUpgradeStatus() + if status == 1 or status == 2 or status == 3 or status == 4 or status == 5 or status == 12 then + return true + else + return false + end +end + +function cancelUpgrade() + local LuciUtil = require("luci.util") + local XQPreference = require("xiaoqiang.XQPreference") + local XQDownloadUtil = require("xiaoqiang.util.XQDownloadUtil") + local checkFlash = os.execute(XQConfigs.FLASH_EXECUTION_CHECK) + if checkFlash ~= 0 then + return false + end + local pid = LuciUtil.exec(XQConfigs.UPGRADE_PID) + local luapid = LuciUtil.exec(XQConfigs.UPGRADE_LUA_PID) + if not XQFunction.isStrNil(pid) then + pid = LuciUtil.trim(pid) + os.execute("kill "..pid) + if not XQFunction.isStrNil(luapid) then + os.execute("kill "..LuciUtil.trim(luapid)) + end + XQDownloadUtil.cancelDownload(XQPreference.get(XQConfigs.PREF_ROM_DOWNLOAD_ID, "")) + XQFunction.sysUnlock() + return true + else + return false + end +end + +--[[ + Temp < 50, 属于正常 + 50 < Temp < 64, 风扇可能工作不正常 + Temp > 64, 不正常风扇或温度传感器坏了 +]]-- +function getCpuTemperature() + local LuciUtil = require("luci.util") + local temperature = LuciUtil.exec(XQConfigs.CPU_TEMPERATURE) + if not XQFunction.isStrNil(temperature) then + temperature = temperature:match('Temperature: (%S+)') + if temperature then + temperature = tonumber(LuciUtil.trim(temperature)) + return temperature + end + end + return 0 +end + +--[[ + simple : 0/1/2 (正常模式,时间长上传log/简单模式,时间短,不上传log/简单模式,时间短,上传log) +]]-- +function getNetworkDetectInfo(simple,target) + local LuciUtil = require("luci.util") + local LuciJson = require("json") + local XQSecureUtil = require("xiaoqiang.util.XQSecureUtil") + local network = {} + local targetUrl = (target == nil or not XQSecureUtil.cmdSafeCheck(target)) and "http://www.baidu.com" or target + if targetUrl and targetUrl:match("http://") == nil and targetUrl:match("https://") == nil then + targetUrl = "http://"..targetUrl + end + local result + if tonumber(simple) == 1 then + result = LuciUtil.exec(XQConfigs.SIMPLE_NETWORK_NOLOG_DETECT.."'"..targetUrl.."'") + elseif tonumber(simple) == 2 then + result = LuciUtil.exec(XQConfigs.SIMPLE_NETWORK_DETECT.."'"..targetUrl.."'") + else + result = LuciUtil.exec(XQConfigs.FULL_NETWORK_DETECT.."'"..targetUrl.."'") + end + if result then + result = LuciJson.decode(LuciUtil.trim(result)) + if result and type(result) == "table" then + local checkInfo = result.CHECKINFO + if checkInfo and type(checkInfo) == "table" then + network["wanLink"] = checkInfo.wanlink == "up" and 1 or 0 + network["wanType"] = checkInfo.wanprotocal or "" + network["pingLost"] = checkInfo.ping:match("(%S+)%%") + network["gw"] = checkInfo.gw:match("(%S+)%%") + network["dns"] = checkInfo.dns == "ok" and 1 or 0 + network["tracer"] = checkInfo.tracer == "ok" and 1 or 0 + network["memory"] = tonumber(checkInfo.memory)*100 + network["cpu"] = tonumber(checkInfo.cpu) + network["disk"] = checkInfo.disk + network["tcp"] = checkInfo.tcp + network["http"] = checkInfo.http + network["ip"] = checkInfo.ip + return network + end + end + end + return nil +end + +function checkSystemStatus() + local LuciUtil = require("luci.util") + local status = {} + status["cpu"] = tonumber(LuciUtil.trim(LuciUtil.exec(XQConfigs.CPU_LOAD_AVG))) or 0 + status["mem"] = tonumber(LuciUtil.trim(LuciUtil.exec(XQConfigs.MEMERY_USAGE))) or 0 + status["link"] = string.upper(LuciUtil.trim(LuciUtil.exec(XQConfigs.WAN_LINK))) == "UP" + status["wan"] = true --tonumber(LuciUtil.trim(LuciUtil.exec(XQConfigs.WAN_UP))) > 0 + status["tmp"] = getCpuTemperature() + return status +end + +--[[ + lan: samba + wan: internet + admin: root + return 0/1 (whitelist/blacklist) +]]-- +function getMacfilterMode(filter) + local LuciUtil = require("luci.util") + local getMode = XQConfigs.GET_LAN_MODE + if filter == "wan" then + getMode = XQConfigs.GET_WAN_MODE + elseif filter == "admin" then + getMode = XQConfigs.GET_ADMIN_MODE + end + local macMode = LuciUtil.exec(getMode) + if macMode then + macMode = LuciUtil.trim(macMode) + if macMode == "whitelist" then + return 0 + else + return 1 + end + end + return false +end + +--[[ + filter : lan/wan/admin + mode : 0/1 (whitelist/blacklist) +]]-- +function setMacfilterMode(filter,mode) + local LuciUtil = require("luci.util") + local setMode + if filter == "lan" then + if tonumber(mode) == 0 then + setMode = XQConfigs.SET_LAN_WHITELIST + else + setMode = XQConfigs.SET_LAN_BLACKLIST + end + elseif filter == "wan" then + if tonumber(mode) == 0 then + setMode = XQConfigs.SET_WAN_WHITELIST + else + setMode = XQConfigs.SET_WAN_BLACKLIST + end + elseif filter == "admin" then + if tonumber(mode) == 0 then + setMode = XQConfigs.SET_ADMIN_WHITELIST + else + setMode = XQConfigs.SET_ADMIN_BLACKLIST + end + end + if setMode and os.execute(setMode) == 0 then + return true + else + return false + end +end + +function getDetectionTimestamp() + local XQPreference = require("xiaoqiang.XQPreference") + return tonumber(XQPreference.get(XQConfigs.PREF_TIMESTAMP, "0")) +end + +function setDetectionTimestamp() + local XQPreference = require("xiaoqiang.XQPreference") + XQPreference.set(XQConfigs.PREF_TIMESTAMP, tostring(os.time())) +end + +function getWifiLog() + os.execute(XQConfigs.WIFI_LOG_COLLECTION) +end + +function getNvramConfigs() + local configs = {} + configs["wifi_ssid"] = XQFunction.nvramGet("nv_wifi_ssid", "") + configs["wifi_enc"] = XQFunction.nvramGet("nv_wifi_enc", "") + configs["wifi_pwd"] = XQFunction.nvramGet("nv_wifi_pwd", "") + configs["rom_ver"] = XQFunction.nvramGet("nv_rom_ver", "") + configs["rom_channel"] = XQFunction.nvramGet("nv_rom_channel", "") + configs["hardware"] = XQFunction.nvramGet("nv_hardware", "") + configs["uboot"] = XQFunction.nvramGet("nv_uboot", "") + configs["linux"] = XQFunction.nvramGet("nv_linux", "") + configs["ramfs"] = XQFunction.nvramGet("nv_ramfs", "") + configs["sqafs"] = XQFunction.nvramGet("nv_sqafs", "") + configs["rootfs"] = XQFunction.nvramGet("nv_rootfs", "") + configs["sys_pwd"] = XQFunction.nvramGet("nv_sys_pwd", "") + configs["wan_type"] = XQFunction.nvramGet("nv_wan_type", "") + configs["pppoe_name"] = XQFunction.nvramGet("nv_pppoe_name", "") + configs["pppoe_pwd"] = XQFunction.nvramGet("nv_pppoe_pwd", "") + return configs +end + +function noflushdStatus() + return os.execute("/etc/init.d/noflushd status") +end + +function noflushdSwitch(on) + if on then + return os.execute("/etc/init.d/noflushd on") == 0 + else + return os.execute("killall -s 10 noflushd ; /etc/init.d/noflushd off") == 0 + end +end + +function getModulesList() + local uci = require("luci.model.uci").cursor() + local result = {} + local modules = uci:get_all("module", "common") + for key, value in pairs(modules) do + if key and value and not key:match("%.") then + result[key] = value + end + end + if _G.next(result) == nil then + return nil + else + return result + end +end + +function facInfo() + local LuciUtil = require("luci.util") + local fac = {} + fac["version"] = getRomVersion() + fac["init"] = getInitInfo() + fac["ssh"] = tonumber(XQFunction.nvramGet("ssh_en", 0)) == 1 and true or false + fac["uart"] = tonumber(XQFunction.nvramGet("uart_en", 0)) == 1 and true or false + fac["telnet"] = tonumber(XQFunction.nvramGet("telnet_en", 0)) == 1 and true or false + fac["facmode"] = tonumber(LuciUtil.exec("cat /proc/xiaoqiang/ft_mode 2>/dev/null")) == 1 and true or false + local start = tonumber(LuciUtil.exec("fdisk -lu | grep /dev/sda4 | awk {'print $2'}")) + if start then + start = math.mod(start ,8) == 0 and true or false + else + start = false + end + fac["4kblock"] = start + return fac +end \ No newline at end of file diff --git a/Mi_Lua/xiaoqiang/util/XQUPnPUtil.lua b/Mi_Lua/xiaoqiang/util/XQUPnPUtil.lua new file mode 100644 index 0000000..ed452c2 --- /dev/null +++ b/Mi_Lua/xiaoqiang/util/XQUPnPUtil.lua @@ -0,0 +1,57 @@ +module ("xiaoqiang.util.XQUPnPUtil", package.seeall) + +local XQFunction = require("xiaoqiang.common.XQFunction") +local XQConfigs = require("xiaoqiang.common.XQConfigs") + +local LuciUtil = require("luci.util") + +function getUPnPStatus() + if os.execute(XQConfigs.UPNP_STATUS) == 0 then + return true + else + return false + end +end + +function switchUPnP(enable) + if enable then + return os.execute(XQConfigs.UPNP_ENABLE) + else + return os.execute(XQConfigs.UPNP_DISABLE) + end +end + +function getUPnPList() + if getUPnPStatus() then + local upnpLease = LuciUtil.exec(XQConfigs.UPNP_LEASE_FILE) + if upnpLease then + upnpLease = LuciUtil.trim(upnpLease) + local upnpFile = io.open(upnpLease,"r") + if upnpFile then + local upnpList = {} + for line in upnpFile:lines() do + if not XQFunction.isStrNil(line) then + local item = {} + local info = LuciUtil.split(line,":") + if #info == 6 then + item["protocol"] = info[1] + item["rport"] = info[2] + item["ip"] = info[3] + item["cport"] = info[4] + item["time"] = info[5] + if info[6] == "(null)" then + item["name"] = "未知程序" + else + item["name"] = info[6] + end + table.insert(upnpList,item) + end + end + end + upnpFile:close() + return upnpList + end + end + end + return nil +end \ No newline at end of file diff --git a/Mi_Lua/xiaoqiang/util/XQVPNUtil.lua b/Mi_Lua/xiaoqiang/util/XQVPNUtil.lua new file mode 100644 index 0000000..ea80dc1 --- /dev/null +++ b/Mi_Lua/xiaoqiang/util/XQVPNUtil.lua @@ -0,0 +1,90 @@ +module ("xiaoqiang.util.XQVPNUtil", package.seeall) + +local XQFunction = require("xiaoqiang.common.XQFunction") +local XQConfigs = require("xiaoqiang.common.XQConfigs") + +local Network = require("luci.model.network") +local Firewall = require("luci.model.firewall") + +-- @param proto pptp/l2tp +-- @param auto 0/1 +function setVpn(interface, server, username, password, proto, auto) + if XQFunction.isStrNil(interface) or XQFunction.isStrNil(server) or XQFunction.isStrNil(username) or XQFunction.isStrNil(password) or XQFunction.isStrNil(proto) then + return false + end + auto = tonumber(auto) + local autoinit = (auto and auto == 0) and "0" or "1" + local protocal = string.lower(proto) + local network = Network.init() + network:del_network(interface) + local vpnNetwork = network:add_network(interface, { + proto = protocal, + server = server, + username = username, + password = password, + auto = autoinit, + auth = 'auto' + }) + if vpnNetwork then + network:save("network") + network:commit("network") + local firewall = Firewall.init() + local zoneWan = firewall:get_zone("wan") + zoneWan:add_network(interface) + firewall:save("firewall") + firewall:commit("firewall") + return true + end + return false +end + +function getVPNInfo(interface) + local network = Network.init() + local info = { + proto = "", + server = "", + username = "", + password = "", + auto = "1" + } + if XQFunction.isStrNil(interface) then + return info + end + local vpn = network:get_network(interface) + if vpn then + info.proto = vpn:get_option_value("proto") + info.server = vpn:get_option_value("server") + info.username = vpn:get_option_value("username") + info.password = vpn:get_option_value("password") + info.auto = vpn:get_option_value("auto") + end + return info +end + +function vpnSwitch(enable) + if enable then + os.execute(XQConfigs.RM_VPNSTATUS_FILE) + os.execute(XQConfigs.VPN_DISABLE) + return (os.execute(XQConfigs.VPN_ENABLE) == 0) + else + os.execute(XQConfigs.RM_VPNSTATUS_FILE) + return (os.execute(XQConfigs.VPN_DISABLE) == 0) + end +end + +function vpnStatus() + local LuciUtil = require("luci.util") + local status = LuciUtil.exec(XQConfigs.VPN_STATUS) + if not XQFunction.isStrNil(status) then + status = LuciUtil.trim(status) + if XQFunction.isStrNil(status) then + return nil + end + local json = require("json") + status = json.decode(status) + if status then + return status + end + end + return nil +end diff --git a/Mi_Lua/xiaoqiang/util/XQWifiUtil.lua b/Mi_Lua/xiaoqiang/util/XQWifiUtil.lua new file mode 100644 index 0000000..992701c --- /dev/null +++ b/Mi_Lua/xiaoqiang/util/XQWifiUtil.lua @@ -0,0 +1,1206 @@ +module ("xiaoqiang.util.XQWifiUtil", package.seeall) + +local XQFunction = require("xiaoqiang.common.XQFunction") +local XQConfigs = require("xiaoqiang.common.XQConfigs") + +local LuciNetwork = require("luci.model.network") +local LuciUtil = require("luci.util") + +local UCI = require("luci.model.uci").cursor() +local WIFI2G = UCI:get("misc", "wireless", "if_2G") +local WIFI5G = UCI:get("misc", "wireless", "if_5G") + +local WIFI_DEVS = { + WIFI2G, + WIFI5G +} + +local WIFI_NETS = { + WIFI2G..".network1", + WIFI5G..".network1" +} + +function getWifiNames() + return WIFI_DEVS, WIFI_NETS +end + +function _wifiNameForIndex(index) + return WIFI_NETS[index] +end + +function wifiNetworks() + local result = {} + local network = LuciNetwork.init() + local dev + for _, dev in ipairs(network:get_wifidevs()) do + local rd = { + up = dev:is_up(), + device = dev:name(), + name = dev:get_i18n(), + networks = {} + } + local wifiNet + for _, wifiNet in ipairs(dev:get_wifinets()) do + rd.networks[#rd.networks+1] = { + name = wifiNet:shortname(), + up = wifiNet:is_up(), + mode = wifiNet:active_mode(), + ssid = wifiNet:active_ssid(), + bssid = wifiNet:active_bssid(), + encryption = wifiNet:active_encryption(), + frequency = wifiNet:frequency(), + channel = wifiNet:channel(), + cchannel = wifiNet:confchannel(), + bw = wifiNet:bw(), + cbw = wifiNet:confbw(), + signal = wifiNet:signal(), + quality = wifiNet:signal_percent(), + noise = wifiNet:noise(), + bitrate = wifiNet:bitrate(), + ifname = wifiNet:ifname(), + assoclist = wifiNet:assoclist(), + country = wifiNet:country(), + txpower = wifiNet:txpower(), + txpoweroff = wifiNet:txpower_offset(), + key = wifiNet:get("key"), + key1 = wifiNet:get("key1"), + encryption_src = wifiNet:get("encryption"), + hidden = wifiNet:get("hidden"), + txpwr = wifiNet:txpwr() + } + end + result[#result+1] = rd + end + return result +end + +function wifiNetwork(wifiDeviceName) + local network = LuciNetwork.init() + local wifiNet = network:get_wifinet(wifiDeviceName) + if wifiNet then + local dev = wifiNet:get_device() + if dev then + return { + id = wifiDeviceName, + name = wifiNet:shortname(), + up = wifiNet:is_up(), + mode = wifiNet:active_mode(), + ssid = wifiNet:active_ssid(), + bssid = wifiNet:active_bssid(), + encryption = wifiNet:active_encryption(), + encryption_src = wifiNet:get("encryption"), + frequency = wifiNet:frequency(), + channel = wifiNet:channel(), + cchannel = wifiNet:confchannel(), + bw = wifiNet:bw(), + cbw = wifiNet:confbw(), + signal = wifiNet:signal(), + quality = wifiNet:signal_percent(), + noise = wifiNet:noise(), + bitrate = wifiNet:bitrate(), + ifname = wifiNet:ifname(), + assoclist = wifiNet:assoclist(), + country = wifiNet:country(), + txpower = wifiNet:txpower(), + txpoweroff = wifiNet:txpower_offset(), + key = wifiNet:get("key"), + key1 = wifiNet:get("key1"), + hidden = wifiNet:get("hidden"), + txpwr = wifiNet:txpwr(), + device = { + up = dev:is_up(), + device = dev:name(), + name = dev:get_i18n() + } + } + end + end + return {} +end + +--[[ +Get devices conneted to wifi +@param wifiIndex: 1 (2.4G)/ 2 (5G) +@return avaliable channel list +]]-- +function getChannels(wifiIndex) + local stat, iwinfo = pcall(require, "iwinfo") + local iface = _wifiNameForIndex(wifiIndex) + local cns + if stat then + local t = iwinfo.type(iface or "") + if iface and t and iwinfo[t] then + cns = iwinfo[t].freqlist(iface) + end + end + return cns +end + +local wifi24 = { + ["1"] = {["20"] = "1", ["40"] = "1l"}, + ["2"] = {["20"] = "2", ["40"] = "2l"}, + ["3"] = {["20"] = "3", ["40"] = "3l"}, + ["4"] = {["20"] = "4", ["40"] = "4l"}, + ["5"] = {["20"] = "5", ["40"] = "5l"}, + ["6"] = {["20"] = "6", ["40"] = "6l"}, + ["7"] = {["20"] = "7", ["40"] = "7l"}, + ["8"] = {["20"] = "8", ["40"] = "8u"}, + ["9"] = {["20"] = "9", ["40"] = "9u"}, + ["10"] = {["20"] = "10", ["40"] = "10u"}, + ["11"] = {["20"] = "11", ["40"] = "11u"}, + ["12"] = {["20"] = "12", ["40"] = "12u"}, + ["13"] = {["20"] = "13", ["40"] = "13u"} +} + +local wifi50 = { + ["36"] = {["20"] = "36", ["40"] = "36l", ["80"] = "36/80"}, + ["40"] = {["20"] = "40", ["40"] = "40u", ["80"] = "40/80"}, + ["44"] = {["20"] = "44", ["40"] = "44l", ["80"] = "44/80"}, + ["48"] = {["20"] = "48", ["40"] = "48u", ["80"] = "48/80"}, + ["52"] = {["20"] = "52", ["40"] = "52l", ["80"] = "52/80"}, + ["56"] = {["20"] = "56", ["40"] = "56u", ["80"] = "56/80"}, + ["60"] = {["20"] = "60", ["40"] = "60l", ["80"] = "60/80"}, + ["64"] = {["20"] = "64", ["40"] = "64u", ["80"] = "64/80"}, + ["149"] = {["20"] = "149", ["40"] = "149l", ["80"] = "149/80"}, + ["153"] = {["20"] = "153", ["40"] = "153u", ["80"] = "153/80"}, + ["157"] = {["20"] = "157", ["40"] = "157l", ["80"] = "157/80"}, + ["161"] = {["20"] = "161", ["40"] = "161u", ["80"] = "161/80"}, + ["165"] = {["20"] = "165"} +} + +function getDefaultWifiChannels(wifiIndex) + if tonumber(wifiIndex) == 1 then + return wifi24 + else + return wifi50 + end +end + +--[[ +Get devices conneted to wifi +@param wifiIndex: 1 (2.4G)/ 2 (5G) +@return divices list +]]-- +function getWifiConnectDeviceList(wifiIndex) + local wifiUp + local assoclist = {} + if tonumber(wifiIndex) == 1 then + wifiUp = (getWifiStatus(1).up == 1) + assoclist = wifiNetwork(_wifiNameForIndex(1)).assoclist or {} + else + wifiUp = (getWifiStatus(2).up == 1) + assoclist = wifiNetwork(_wifiNameForIndex(2)).assoclist or {} + end + local dlist = {} + if wifiUp then + for mac, info in pairs(assoclist) do + table.insert(dlist, XQFunction.macFormat(mac)) + end + end + return dlist +end + +function isDeviceWifiConnect(mac,wifiIndex) + local dict = getWifiConnectDeviceDict(wifiIndex) + if type(dict) == "table" and #dict>0 then + return dict[XQFunction.macFormat(mac)] ~= nil + else + return false + end +end + +--[[ +Get devices conneted to wifi +@param wifiIndex: 1 (2.4G)/ 2 (5G) +@return divices dict{mac:1} +]]-- +function getWifiConnectDeviceDict(wifiIndex) + local wifiUp + local assoclist = {} + if tonumber(wifiIndex) == 1 then + wifiUp = (getWifiStatus(1).up == 1) + assoclist = wifiNetwork(_wifiNameForIndex(1)).assoclist or {} + else + wifiUp = (getWifiStatus(2).up == 1) + assoclist = wifiNetwork(_wifiNameForIndex(2)).assoclist or {} + end + local dict = {} + if wifiUp then + for mac, info in pairs(assoclist) do + if mac then + dict[XQFunction.macFormat(mac)] = 1 + end + end + end + return dict +end + +function _pauseChannel(channel) + if XQFunction.isStrNil(channel) then + return "" + end + if channel:match("l") then + return channel:gsub("l","").."(40M)" + end + if channel:match("u") then + return channel:gsub("u","").."(40M)" + end + if channel:match("\/80") then + return channel:gsub("\/80","").."(80M)" + end + return channel.."(20M)" +end + +function getWifiWorkChannel(wifiIndex) + local channel = "" + if tonumber(wifiIndex) == 1 then + channel = LuciUtil.trim(LuciUtil.exec(XQConfigs.WIFI24_WORK_CHANNEL)) + else + channel = LuciUtil.trim(LuciUtil.exec(XQConfigs.WIFI50_WORK_CHANNEL)) + end + return _pauseChannel(channel) +end + +--[[ +Get device wifiIndex +@param mac: mac address +@return 0 (lan)/1 (2.4G)/ 2 (5G) +]]-- +function getDeviceWifiIndex(mac) + mac = XQFunction.macFormat(mac) + local wifi1Devices = getWifiConnectDeviceDict(1) + local wifi2Devices = getWifiConnectDeviceDict(2) + if wifi1Devices then + if wifi1Devices[mac] == 1 then + return 1 + end + end + if wifi2Devices then + if wifi2Devices[mac] == 1 then + return 2 + end + end + return 0 +end + +function getWifiDeviceSignalDict(wifiIndex) + local result = {} + local assoclist = {} + if not (getWifiStatus(wifiIndex).up == 1) then + return result + end + if wifiIndex == 1 then + assoclist = wifiNetwork(_wifiNameForIndex(1)).assoclist or {} + else + assoclist = wifiNetwork(_wifiNameForIndex(2)).assoclist or {} + end + for mac, info in pairs(assoclist) do + if mac then + result[XQFunction.macFormat(mac)] = 2*math.abs(tonumber(info.signal)-tonumber(info.noise)) + end + end + return result +end + +--[[ +Get all devices conneted to wifi +@return devices list [{mac,signal,wifiIndex}..] +]]-- +function getAllWifiConnetDeviceList() + local result = {} + for index = 1,2 do + local wifiSignal = getWifiDeviceSignalDict(index) + local wifilist = getWifiConnectDeviceList(index) + for _, mac in pairs(wifilist) do + table.insert(result, { + ['mac'] = XQFunction.macFormat(mac), + ['signal'] = wifiSignal[mac], + ['wifiIndex'] = index + }) + end + end + return result +end + +--[[ +Get all devices conneted to wifi +@return devices dict{mac:{signal,wifiIndex}} +]]-- +function getAllWifiConnetDeviceDict() + local result = {} + for index = 1,2 do + local wifiSignal = getWifiDeviceSignalDict(index) + local wifilist = getWifiConnectDeviceList(index) + for _, mac in pairs(wifilist) do + local item = {} + item['signal'] = wifiSignal[mac] + item['wifiIndex'] = index + result[XQFunction.macFormat(mac)] = item + end + end + return result +end + +--[[ +Get wifi status +@param wifiIndex: 1 (2.4G)/ 2 (5G) +@return dict{ssid,up} +]]-- +function getWifiStatus(wifiIndex) + local wifiNet = wifiNetwork(_wifiNameForIndex(wifiIndex)) + return { + ['ssid'] = wifiNet["ssid"], + ['up'] = wifiNet["up"] and 1 or 0 + } +end + +function channelHelper(channel) + local channelInfo = {channel = "", bandwidth = ""} + if XQFunction.isStrNil(channel) then + return channelInfo + end + if string.find(channel,"l") ~= nil then + channelInfo["channel"] = channel:match("(%S+)l") + channelInfo["bandwidth"] = "40" + elseif string.find(channel,"u") ~= nil then + channelInfo["channel"] = channel:match("(%S+)u") + channelInfo["bandwidth"] = "40" + elseif string.find(channel,"/80") ~= nil then + channelInfo["channel"] = channel:match("(%S+)/80") + channelInfo["bandwidth"] = "80" + else + channelInfo["channel"] = tostring(channel) + channelInfo["bandwidth"] = "20" + end + local bandList = {} + if channelInfo.channel then + local channelList = wifi24[channelInfo.channel] or wifi50[channelInfo.channel] + if channelList and type(channelList) == "table" then + for key, v in pairs(channelList) do + table.insert(bandList, key) + end + end + end + channelInfo["bandList"] = bandList + return channelInfo +end + +function getBandList(channel) + local channelInfo = {channel = "", bandwidth = ""} + if XQFunction.isStrNil(channel) then + return channelInfo + end + local bandList = {} + local channelList = wifi24[tostring(channel)] or wifi50[tostring(channel)] + if channelList and type(channelList) == "table" then + for key, v in pairs(channelList) do + table.insert(bandList, key) + end + end + channelInfo["bandList"] = bandList + return channelInfo +end + +function _channelFix(channel) + if XQFunction.isStrNil(channel) then + return "" + end + channel = string.gsub(channel, "l", "") + channel = string.gsub(channel, "u", "") + channel = string.gsub(channel, "/80", "") + return channel +end + +function channelFormat(wifiIndex, channel, bandwidth) + local channelList = {} + if tonumber(wifiIndex) == 1 then + channelList = wifi24[tostring(channel)] + else + channelList = wifi50[tostring(channel)] + end + if channelList and type(channelList) == "table" then + local channel = channelList[tostring(bandwidth)] + if not XQFunction.isStrNil(channel) then + return channel + end + end + return false +end + +--[[ +Get wifi information +@return dict{status,ifname,device,ssid,encryption,channel,mode,hidden,signal,password} +]]-- +function getAllWifiInfo() + local infoList = {} + local wifis = wifiNetworks() + for i,wifiNet in ipairs(wifis) do + local item = {} + local index = 1 + if wifiNet["up"] then + item["status"] = "1" + else + item["status"] = "0" + end + local encryption = wifiNet.networks[index].encryption_src + local key = wifiNet.networks[index].key + if encryption == "wep-open" then + key = wifiNet.networks[index].key1 + if key:len()>4 and key:sub(0,2)=="s:" then + key = key:sub(3) + end + end + local channel = wifiNet.networks[index].cchannel + local channelparseinfo = channelHelper(channel) + item["ifname"] = wifiNet.networks[index].ifname + item["device"] = wifiNet.device..".network"..index + item["ssid"] = wifiNet.networks[index].ssid + item["channel"] = channelparseinfo.channel + item["bandwidth"] = wifiNet.networks[index].cbw or channelparseinfo.bandwidth or "0" + item["channelInfo"] = getBandList(channel) + item["channelInfo"]["channel"] = wifiNet.networks[index].channel + item["channelInfo"]["bandwidth"] = wifiNet.networks[index].bw or "20" + item["mode"] = wifiNet.networks[index].mode + item["hidden"] = wifiNet.networks[index].hidden or 0 + item["signal"] = wifiNet.networks[index].signal + item["password"] = key + item["encryption"] = encryption + item["txpwr"] = wifiNet.networks[index].txpwr + infoList[#wifis+1-i] = item + end + local guestwifi = getGuestWifi(1) + if guestwifi then + table.insert(infoList, guestwifi) + end + return infoList +end + +function getWifiTxpwr(wifiIndex) + local network = LuciNetwork.init() + local wifiNet = network:get_wifinet(_wifiNameForIndex(wifiIndex)) + if wifiNet then + return tostring(wifiNet:txpwr()) + else + return nil + end +end + +function getWifiChannel(wifiIndex) + local network = LuciNetwork.init() + local wifiNet = network:get_wifinet(_wifiNameForIndex(wifiIndex)) + if wifiNet then + return tostring(wifiNet:channel()) + else + return nil + end +end + +function getWifiTxpwrList() + local txpwrList = {} + local network = LuciNetwork.init() + local wifiNet1 = network:get_wifinet(_wifiNameForIndex(1)) + local wifiNet2 = network:get_wifinet(_wifiNameForIndex(2)) + if wifiNet1 then + table.insert(txpwrList,tostring(wifiNet1:txpwr())) + end + if wifiNet2 then + table.insert(txpwrList,tostring(wifiNet2:txpwr())) + end + return txpwrList +end + +function getWifiChannelList() + local channelList = {} + local network = LuciNetwork.init() + local wifiNet1 = network:get_wifinet(_wifiNameForIndex(1)) + local wifiNet2 = network:get_wifinet(_wifiNameForIndex(2)) + if wifiNet1 then + table.insert(channelList,tostring(wifiNet1:channel())) + end + if wifiNet2 then + table.insert(channelList,tostring(wifiNet2:channel())) + end + return channelList +end + +function getWifiChannelTxpwrList() + local result = {} + local network = LuciNetwork.init() + local wifiNet1 = network:get_wifinet(_wifiNameForIndex(1)) + local wifiNet2 = network:get_wifinet(_wifiNameForIndex(2)) + if wifiNet1 then + table.insert(result,{ + channel = tostring(wifiNet1:channel()), + txpwr = tostring(wifiNet1:txpwr()) + }) + else + table.insert(result,{}) + end + if wifiNet2 then + table.insert(result,{ + channel = tostring(wifiNet2:channel()), + txpwr = tostring(wifiNet2:txpwr()) + }) + else + table.insert(result,{}) + end + return result +end + +function setWifiChannelTxpwr(channel1,txpwr1,channel2,txpwr2) + local network = LuciNetwork.init() + local wifiDev1 = network:get_wifidev(LuciUtil.split(_wifiNameForIndex(1),".")[1]) + local wifiDev2 = network:get_wifidev(LuciUtil.split(_wifiNameForIndex(2),".")[1]) + if wifiDev1 then + if tonumber(channel1) then + wifiDev1:set("channel",channel1) + end + if not XQFunction.isStrNil(txpwr1) then + wifiDev1:set("txpwr",txpwr1); + end + end + if wifiDev2 then + if tonumber(channel2) then + wifiDev2:set("channel",channel2) + end + if not XQFunction.isStrNil(txpwr2) then + wifiDev2:set("txpwr",txpwr2); + end + end + network:commit("wireless") + network:save("wireless") + return true +end + +function setWifiTxpwr(txpwr) + local network = LuciNetwork.init() + local wifiDev1 = network:get_wifidev(LuciUtil.split(_wifiNameForIndex(1),".")[1]) + local wifiDev2 = network:get_wifidev(LuciUtil.split(_wifiNameForIndex(2),".")[1]) + if wifiDev1 then + if not XQFunction.isStrNil(txpwr) then + wifiDev1:set("txpwr",txpwr); + end + end + if wifiDev2 then + if not XQFunction.isStrNil(txpwr) then + wifiDev2:set("txpwr",txpwr); + end + end + network:commit("wireless") + network:save("wireless") + return true +end + +function checkWifiPasswd(passwd,encryption) + if XQFunction.isStrNil(encryption) or (encryption and encryption ~= "none" and XQFunction.isStrNil(passwd)) then + return 1502 + end + if encryption == "psk" or encryption == "psk2" then + if passwd:len() < 8 then + return 1520 + end + elseif encryption == "mixed-psk" then + if passwd:len()<8 or passwd:len()>63 then + return 1521 + end + elseif encryption == "wep-open" then + if passwd:len()~=5 and passwd:len()~=13 then + return 1522 + end + end + return 0 +end + +function checkSSID(ssid,length) + if XQFunction.isStrNil(ssid) then + return 0 + end + if string.len(ssid) > tonumber(length) then + return 1572 + end + if not XQFunction.checkSSID(ssid) then + return 1573 + end + return 0 +end + +function setWifiBasicInfo(wifiIndex, ssid, password, encryption, channel, txpwr, hidden, on, bandwidth) + local network = LuciNetwork.init() + local wifiNet = network:get_wifinet(_wifiNameForIndex(wifiIndex)) + local wifiDev = network:get_wifidev(LuciUtil.split(_wifiNameForIndex(wifiIndex),".")[1]) + if wifiNet == nil then + return false + end + if wifiDev then + if not XQFunction.isStrNil(channel) then + wifiDev:set("channel",channel) + if channel == "0" then + wifiDev:set("autoch","2") + else + wifiDev:set("autoch","0") + end + end + if not XQFunction.isStrNil(bandwidth) then + wifiDev:set("bw",bandwidth) + end + if not XQFunction.isStrNil(txpwr) then + wifiDev:set("txpwr",txpwr); + end + if on == 1 then + wifiDev:set("disabled", "0") + elseif on == 0 then + wifiDev:set("disabled", "1") + end + end + wifiNet:set("disabled", nil) + if not XQFunction.isStrNil(ssid) and XQFunction.checkSSID(ssid) then + wifiNet:set("ssid",ssid) + end + local code = checkWifiPasswd(password,encryption) + if code == 0 then + wifiNet:set("encryption",encryption) + wifiNet:set("key",password) + if encryption == "none" then + wifiNet:set("key","") + elseif encryption == "wep-open" then + wifiNet:set("key1","s:"..password) + wifiNet:set("key",1) + end + if wifiIndex == 1 then + XQFunction.nvramSet("nv_wifi_ssid", ssid) + XQFunction.nvramSet("nv_wifi_enc", encryption) + XQFunction.nvramSet("nv_wifi_pwd", password) + XQFunction.nvramCommit() + else + XQFunction.nvramSet("nv_wifi_ssid1", ssid) + XQFunction.nvramSet("nv_wifi_enc1", encryption) + XQFunction.nvramSet("nv_wifi_pwd1", password) + XQFunction.nvramCommit() + end + elseif code > 1502 then + return false + end + if hidden == "1" then + wifiNet:set("hidden","1") + end + if hidden == "0" then + wifiNet:set("hidden","0") + end + network:save("wireless") + network:commit("wireless") + return true +end + +--[[ +Turn on wifi +@param wifiIndex: 1 (2.4G)/ 2 (5G) +@return boolean +]]-- +function turnWifiOn(wifiIndex) + local wifiStatus = getWifiStatus(wifiIndex) + if wifiStatus['up'] == 1 then + return true + end + local network = LuciNetwork.init() + local wifiNet = network:get_wifinet(_wifiNameForIndex(wifiIndex)) + local dev + if wifiNet ~= nil then + dev = wifiNet:get_device() + end + if dev and wifiNet then + dev:set("disabled", "0") + wifiNet:set("disabled", nil) + network:commit("wireless") + XQFunction.forkRestartWifi() + return true + end + return false +end + +--[[ +Turn off wifi +@param wifiIndex: 1 (2.4G)/ 2 (5G) +@return boolean +]]-- +function turnWifiOff(wifiIndex) + local wifiStatus = getWifiStatus(wifiIndex) + if wifiStatus['up'] == 0 then + return true + end + + local network = LuciNetwork.init() + local wifiNet = network:get_wifinet(_wifiNameForIndex(wifiIndex)) + local dev + if wifiNet ~= nil then + dev = wifiNet:get_device() + end + if dev and wifiNet then + dev:set("disabled", "1") + wifiNet:set("disabled", nil) + network:commit("wireless") + XQFunction.forkRestartWifi() + return true + end + return false +end + +function wifiScanList(wifiIndex) + local LuciSys = require("luci.sys") + local LuciUtil = require("luci.util") + local scanList = {} + local iw = LuciSys.wifi.getiwinfo(_wifiNameForIndex(wifiIndex)) + if iw then + for i, wifi in ipairs(iw.scanlist or { }) do + local wifiDev = {} + local quality = wifi.quality or 0 + local qualityMax = wifi.quality_max or 0 + local wifiSigPercent = 0 + if wifi.bssid and quality > 0 and qualityMax > 0 then + wifiSigPercent = math.floor((100 / qualityMax) * quality) + end + wifi.encryption = wifi.encryption or { } + wifiDev["ssid"] = wifi.ssid and LuciUtil.pcdata(wifi.ssid) or "hidden" + wifiDev["bssid"] = wifi.bssid + wifiDev["mode"] = wifi.mode + wifiDev["channel"] = wifi.channel + wifiDev["encryption"] = wifi.encryption + wifiDev["signal"] = wifi.signal or 0 + wifiDev["signalPercent"] = wifiSigPercent + wifiDev["quality"] = quality + wifiDev["qualityMax"] = qualityMax + table.insert(scanList,wifiDev) + end + end + return scanList +end + +function wifiBridgedClientId() + local LuciNetwork = require("luci.model.network").init() + local wifiDevs = LuciNetwork:get_wifidevs(); + local clients = {} + for i, wifiDev in ipairs(wifiDevs) do + local clientId + for _, wifiNet in ipairs(wifiDev:get_wifinets()) do + if wifiNet:active_mode() == "Client" then + clientId = wifiNet:id() + end + end + if not XQFunction.isStrNil(clientId) then + table.insert(clients,i,clientId) + end + end + return clients +end + +--[[ +@param wifiIndex : 1(2.4G) 2(5G) +function getWifiBridgedClient(wifiIndex) + local LuciNetwork = require("luci.model.network").init() + local client = {} + local clientId = wifiBridgedClientId()[wifiIndex] + if clientId then + local wifiNet = LuciNetwork:get_wifinet(clientId) + if wifiNet:get("disabled") == "1" then + return client + end + client["ssid"] = wifiNet:get("ssid") + client["key"] = wifiNet:get("key") + client["encryption"] = wifiNet:get("encryption") + client["channel"] = wifiNet:get("channel") + end + return client +end +]]-- +--[[ +@param wifiIndex : 1(2.4G) 2(5G) +function setWifiBridgedClient(wifiIndex,ssid,encryption,key,channel) + local LuciNetwork = require("luci.model.network").init() + local XQLanWanUtil = require("xiaoqiang.util.XQLanWanUtil") + -- Set wifi + local clientId = wifiBridgedClientId()[wifiIndex] + local wifiDev = LuciNetwork:get_wifidev(LuciUtil.split(_wifiNameForIndex(wifiIndex),".")[1]) + local wlanX = string.format("wlan%d",wifiIndex+1) + if not wifiDev then + return false + end + if XQFunction.isStrNil(clientId) then + local network = {ifname = wlanX,ssid = ssid,mode = "sta",encryption = encryption,key = key,network = "",disabled = "0"} + wifiDev:add_wifinet(network) + else + local wifiNet = wifiDev:get_wifinet(clientId) + wlanX = wifiNet:get("ifname") + wifiNet:set("ssid",ssid) + wifiNet:set("key",key) + wifiNet:set("encryption",encryption) + wifiNet:set("network","") + wifiNet:set("disabled","0") + end + wifiDev:set("channel",channel) + -- Set wan + local wanMac = XQLanWanUtil.getWanMac() + local wanNetwork = {ifname = wlanX, macaddr = wanMac, proto = "dhcp"} + LuciNetwork:del_network("wan") + LuciNetwork:add_network("wan",wanNetwork) + -- Save and commit + LuciNetwork:save("wireless") + LuciNetwork:save("network") + LuciNetwork:commit("wireless") + LuciNetwork:commit("network") +end +]]-- + +--[[ +@return 0:close 1:start 2:connect 3:error 4:timeout +]] +function getWifiWpsStatus() + local LuciUtil = require("luci.util") + local status = LuciUtil.exec(XQConfigs.GET_WPS_STATUS) + if not XQFunction.isStrNil(status) then + status = LuciUtil.trim(status) + return tonumber(status) + end + return 0 +end + +function getWpsConDevMac() + local LuciUtil = require("luci.util") + local mac = LuciUtil.exec(XQConfigs.GET_WPS_CONMAC) + if mac then + return XQFunction.macFormat(LuciUtil.trim(mac)) + end + return nil +end + +function stopWps() + local LuciUtil = require("luci.util") + LuciUtil.exec(XQConfigs.CLOSE_WPS) + return +end + +function openWifiWps() + local LuciUtil = require("luci.util") + local XQPreference = require("xiaoqiang.XQPreference") + LuciUtil.exec(XQConfigs.OPEN_WPS) + local timestamp = tostring(os.time()) + XQPreference.set(XQConfigs.PREF_WPS_TIMESTAMP,timestamp) + return timestamp +end + +--[[ + WiFi Bridge +]]-- + +--local WIFI_LIST_CMD = [[iwpriv wl1 set SiteSurvey=1;iwpriv wl1 get_site_survey | awk '{print $2"|||"$3"|||"$4"|||"$5}']] +--[[ +function _parseEncryption(encryption) + if XQFunction.isStrNil(encryption) then + return nil + end + encryption = string.lower(encryption) + if encryption:match("none") then + return "NONE" + end + if encryption:match("wpa2psk") then + return "WPA2PSK" + end + if encryption:match("wpapsk") then + return "WPAPSK" + end + if encryption:match("wpa2") then + return "WPA2" + end + if encryption:match("wpa1") then + return "WPA1" + end + return "NONE" +end + +function getWifiScanList() + local wifilist = {} + local LuciUtil = require("luci.util") + for _, line in ipairs(LuciUtil.execl(WIFI_LIST_CMD)) do + local item = LuciUtil.split(line, "|||") + if tonumber(item[4]) then + local wifi = {["ssid"] = "", ["encryption"] = "", ["bssid"] = "", ["signal"] = 0} + local enc = _parseEncryption(item[3]) + if enc then + wifi.encryption = enc + end + wifi.ssid = item[1] + wifi.bssid = item[2] + wifi.signal = tonumber(item[4]) + table.insert(wifilist, wifi) + end + end + return wifilist +end +]]-- +--[[ + @param wifiIndex:1/2 2.4G/5G +]] +function setWifiBridge(wifiIndex, ssid, password, encryption) + local LuciUtil = require("luci.util") + local LuciNetwork = require("luci.model.network").init() + local wifiDev = LuciNetwork:get_wifidev(LuciUtil.split(_wifiNameForIndex(wifiIndex),".")[1]) + local clients = {} + local key + if encryption == "none" then + key = "" + else + key = password + end + if wifiDev then + local clientId + for _, wifiNet in ipairs(wifiDev:get_wifinets()) do + if wifiNet:active_mode() == "Client" then + clientId = wifiNet:id() + end + end + if XQFunction.isStrNil(clientId) then + local iface = { + device = wifiDev:name(), + ifname = "apcli0", + ssid = ssid, + mode = "sta", + encryption = encryption, + key = key, + network = "wan", + disabled = "0" + } + wifiDev:add_wifinet(iface) + else + local wifiNet = wifiDev:get_wifinet(clientId) + wifiNet:set("ssid", ssid) + wifiNet:set("key", key) + wifiNet:set("encryption", encryption) + end + LuciNetwork:save("wireless") + LuciNetwork:commit("wireless") + end +end + +--- model: 0/1 black/white list +function getWiFiMacfilterList(model) + local uci = require("luci.model.uci").cursor() + local config = tonumber(model) == 0 and "wifiblist" or "wifiwlist" + local maclist = uci:get_list(config, "maclist", "mac") + return maclist +end + +--- model: 0/1 black/white list +--- option: 0/1 add/remove +function editWiFiMacfilterList(model, mac, option) + if XQFunction.isStrNil(mac) or XQFunction.isStrNil(option) then + return + else + mac = XQFunction.macFormat(mac) + option = tonumber(option) + end + local uci = require("luci.model.uci").cursor() + local config = tonumber(model) == 0 and "wifiblist" or "wifiwlist" + local maclist = uci:get_list(config, "maclist", "mac") or {} + if option == 0 then + for _, macaddr in ipairs(maclist) do + if mac == macaddr then + return + end + end + table.insert(maclist, mac) + else + local pos = -1 + for i, macaddr in ipairs(maclist) do + if mac == macaddr then + pos = i + end + end + if pos >= 0 then + table.remove(maclist, pos) + else + return + end + end + if #maclist > 32 then + return 1 + end + if #maclist > 0 then + uci:set_list(config, "maclist", "mac", maclist) + else + uci:delete(config, "maclist", "mac") + end + uci:commit(config) +end + +--- model: 0/1 black/white list +function getWiFiMacfilterInfo(model) + local LuciUtil = require("luci.util") + local LuciNetwork = require("luci.model.network").init() + local wifiNet = LuciNetwork:get_wifinet(_wifiNameForIndex(1)) + local info = { + ["enable"] = 0, + ["model"] = 0 + } + if wifiNet then + local macfilter = wifiNet:get("macfilter") + if macfilter == "disabled" then + info["enable"] = 0 + info["model"] = 0 + elseif macfilter == "deny" then + info["enable"] = 1 + info["model"] = 0 + elseif macfilter == "allow" then + info["enable"] = 1 + info["model"] = 1 + else + info["enable"] = 0 + info["model"] = 0 + end + end + if model then + info["maclist"] = getWiFiMacfilterList(model) + else + info["maclist"] = getWiFiMacfilterList(info.model) + end + return info +end + +--- model: 0/1 black/white list +function setWiFiMacfilterModel(enable, model) + local macfilter + local maclist + if enable then + if tonumber(model) == 1 then + macfilter = "allow" + maclist = getWiFiMacfilterList(1) + else + macfilter = "deny" + maclist = getWiFiMacfilterList(0) + end + else + macfilter = "disabled" + end + local LuciUtil = require("luci.util") + local LuciNetwork = require("luci.model.network").init() + local wifiNet1 = LuciNetwork:get_wifinet(_wifiNameForIndex(1)) + local wifiNet2 = LuciNetwork:get_wifinet(_wifiNameForIndex(2)) + if wifiNet1 and wifiNet2 then + wifiNet1:set("macfilter", macfilter) + wifiNet2:set("macfilter", macfilter) + if maclist and #maclist > 0 then + wifiNet1:set_list("maclist", maclist) + wifiNet2:set_list("maclist", maclist) + else + wifiNet1:set_list("maclist", nil) + wifiNet2:set_list("maclist", nil) + end + LuciNetwork:save("wireless") + LuciNetwork:commit("wireless") + end +end + +function getGuestWifi(wifiIndex) + local uci = require("luci.model.uci").cursor() + local index = tonumber(wifiIndex) + local status + if index then + status = getWifiStatus(index) + if index == 1 then + index = "guest_2G" + elseif index == 2 then + index = "guest_5G" + else + index = nil + end + end + local guestwifi + if index and status then + guestwifi = uci:get_all("wireless", index) + if guestwifi then + local enabled + if status.up == 1 and tonumber(guestwifi.enabled) == 1 then + enabled = "1" + else + enabled = "0" + end + return { + ["ifname"] = guestwifi.device, + ["ssid"] = guestwifi.ssid, + ["encryption"] = guestwifi.encryption, + ["password"] = guestwifi.key, + ["status"] = guestwifi.open, + ["enabled"] = enabled + } + end + end + return guestwifi +end + +function setGuestWifi(wifiIndex, ssid, encryption, key, enabled, open) + local uci = require("luci.model.uci").cursor() + local wifinetid, ifname + local enabled = tonumber(enabled) == 1 and 1 or 0 + local open = tonumber(open) == 1 and 1 or 0 + if tonumber(wifiIndex) == 1 then + wifinetid = "guest_2G" + ifname = uci:get("misc", "wireless", "ifname_guest_2G") + elseif tonumber(wifiIndex) == 2 then + wifinetid = "guest_5G" + else + return false + end + guestwifi = uci:get_all("wireless", wifinetid) + if guestwifi then + guestwifi["ifname"] = ifname + if not XQFunction.isStrNil(ssid) and XQFunction.checkSSID(ssid) then + guestwifi["ssid"] = ssid + end + if encryption and string.lower(tostring(encryption)) == "none" then + guestwifi["encryption"] = "none" + guestwifi["key"] = "" + end + if open then + guestwifi["open"] = open + end + if encryption and string.lower(tostring(encryption)) ~= "none" and not XQFunction.isStrNil(key) then + local check = checkWifiPasswd(key,encryption) + if check == 0 then + guestwifi["encryption"] = encryption + guestwifi["key"] = key + else + return false + end + end + else + if XQFunction.isStrNil(ssid) or XQFunction.isStrNil(encryption) then + return false + end + guestwifi = { + ["device"] = WIFI_DEVS[wifiIndex], + ["ifname"] = ifname, + ["network"] = "guest", + ["ssid"] = ssid, + ["mode"] = "ap", + ["encryption"] = encryption, + ["key"] = key, + ["open"] = open, + ["enabled"] = enabled + } + end + uci:section("wireless", "wifi-iface", wifinetid, guestwifi) + uci:commit("wireless") + return true +end + +function delGuestWifi(wifiIndex) + local uci = require("luci.model.uci").cursor() + local wifinetid + if tonumber(wifiIndex) == 1 then + wifinetid = "guest_2G" + elseif tonumber(wifiIndex) == 2 then + wifinetid = "guest_5G" + else + return false + end + uci:delete("wireless", wifinetid) + uci:commit("wireless") + return true +end \ No newline at end of file diff --git a/Mi_Lua/xiaoqiang/util/XQZigbeeUtil.lua b/Mi_Lua/xiaoqiang/util/XQZigbeeUtil.lua new file mode 100644 index 0000000..b2fb0f7 --- /dev/null +++ b/Mi_Lua/xiaoqiang/util/XQZigbeeUtil.lua @@ -0,0 +1,65 @@ +module ("xiaoqiang.util.XQZigbeeUtil", package.seeall) + +local JSON = require("luci.json") +local XQLog = require("xiaoqiang.XQLog") +local XQConfigs = require("xiaoqiang.common.XQConfigs") +local XQFunction = require("xiaoqiang.common.XQFunction") +local XQDeviceUtil = require("xiaoqiang.util.XQDeviceUtil") + + +function request_yeelink(payload) + local XQCryptoUtil = require("xiaoqiang.util.XQCryptoUtil") + local payload1 = XQCryptoUtil.binaryBase64Enc(payload) + local cmd = XQConfigs.THRIFT_TUNNEL_TO_MIIO % payload1 + local LuciUtil = require("luci.util") + return JSON.decode(LuciUtil.exec(cmd)) +end + +function get_zigbee_count() + local device_list = request_yeelink("{\"command\":\"device_list\"}") + if device_list == nil or device_list.list == nil then + return 0 + end + return #device_list.list +end + +function append_yeelink_list(list) + local device_list = request_yeelink("{\"command\":\"device_list\"}") + if device_list == nil or device_list.list == nil or list == nil then + return + end + for _,item in ipairs(device_list.list) do + local it = {} + it.mac = item.mac + it.type = "zigbee" + it.ctype = 4 -- yeelink + it.ptype = 3 -- light + it.online = 0 + + it.origin_name = item.type + it.origin_info = item + + local company = {} + if item.type == "light" then + it.name = "智能灯泡" + company.icon = "device_list_intelligent_lamp.png" + company.name = "Yeelink" + end + it.company = company + + + + local deviceInfoDict = XQDeviceUtil.getDeviceInfoFromDB() + local deviceInfo = deviceInfoDict[it.mac] + if deviceInfo ~= nil and not XQFunction.isStrNil(deviceInfo.nickname) then + it.name = deviceInfoDict[it.mac].nickname + end + + if not deviceInfo then + local XQDBUtil = require("xiaoqiang.util.XQDBUtil") + XQDBUtil.saveDeviceInfo(it.mac,it.origin_name,"","","") + end + + table.insert(list,it) + end +end \ No newline at end of file diff --git a/Mi_Lua/xqcrypto.so b/Mi_Lua/xqcrypto.so new file mode 100644 index 0000000000000000000000000000000000000000..9d0f7b5db8115fb1f710673f5ae1cbaac101e906 GIT binary patch literal 30016 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 D9yev| literal 0 HcmV?d00001 diff --git a/Mi_Lua/xssFilter.lua b/Mi_Lua/xssFilter.lua new file mode 100644 index 0000000..32dab21 --- /dev/null +++ b/Mi_Lua/xssFilter.lua @@ -0,0 +1,325 @@ +----------------------------------------------------------------------------- +-- Filters XHTML removing all tags that are not explicitly allowed. +-- +-- The function parse_xml() is adapted from Roberto Ierusalmischy's collect() +-- (see http://lua-users.org/wiki/LuaXml). +-- +-- (c) 2007, 2008 Yuri Takhteyev (yuri@freewisdom.org) +-- +-- License: MIT/X, see http://sputnik.freewisdom.org/en/License +----------------------------------------------------------------------------- + +module("xssFilter", package.seeall) + +local iconv_loaded, iconv = pcall(require, "iconv") + +----------------------------------------------------------------------------- +-- Default configuration of which tags are allowed. The client can override +-- this by passing their own table. The table allows two types of entries: +-- simply entering the name of the tag as string, allows this tag to be used +-- but without any attributes, except for those attributes listed in +-- GENERIC_ATTRIBUTES and allowed for _all_ tags. Alternatively, the tag can +-- be entered as a table, keyed with the tag name, which can specify what +-- attributes the tag can have, specifying for each attribute the pattern +-- with which its values must start (use "." to allow _any_ values). +-- Additionally, _test can be set to a function that does a more complex +-- evaluation of whether the tag's attributes should be allowed. +-- +-- @see GENERIC_ATTRIBUTES +----------------------------------------------------------------------------- + +----------------------------------------------------------------------------- +-- Returns HTML to be used for replacing bad tags. +-- +-- @param tag tag name. +-- @param message [optional] an explanation for why the tag was removed. +-- @param text source text without tags +-- @return replacement HTML. +----------------------------------------------------------------------------- +REPLACE_TAGS = function(self, tag, message, text) + local buffer = "[HTML tag <"..tag.."> removed" + if message then + buffer = buffer..": "..message + end + return buffer.."]" +end +REMOVE_TAGS = function(self, tag, message, text) + return text +end + +ALLOWED_TAGS = { + + -- Simple tags allowed without any attributes other than those listed in + -- GENERIC_ATTRIBUTES + "p", + "h1", "h2", "h3", "h4", "h5", "h6", + "ul", "ol", "li", "dl", "dt", "dd", + "br", "em", "strong", "i", "b", + "blockquote", + "pre", "code", + "acronym", "abbr", "cite", "dfn", "tt", "del", "ins", "kbd", "strike", + "sub", "sup", "var", + "table", "tr", "thead", "caption", "tbody", "tfoot", + "big", "center", "right", "left", + "hr", + "style", + "div", + -- For "a" tag allow "name" and "href", and limit href to three protocols. + a = { + name = ".", + href= {"^http://", "^https://", "^ftp://", "^mailto:", "^/", "#"}, + }, + -- For "img" tag allow only "src" and limit it to http. + img = { + src = {"^http://", "^/"}, + }, + -- Style is allowed on "span" as long as the string "url" does not occur + -- in the value + span = { + style=".", + _test = function(tag) + if not tag.xarg.style or not tag.xarg.style:find("url") then + return true + else + return nil, "'url' not allowed in the value of 'style'" + end + end + }, + -- Enable the colspan/rowspan attributes for table elements + th = { + colspan = ".", + rowspan = ".", + }, + td = { + colspan = ".", + rowspan = ".", + } +} + +----------------------------------------------------------------------------- +-- Extra tags (disabled by default), allow then at your own risk. +----------------------------------------------------------------------------- +EXTRA_TAGS = { + -- Allow "object" if the it's type is "image/svg+xml" (not that this could + -- be overriden by setting "filter.allowed_tags.object = false" + object = { + data = "http://", + _test = function(tag) + if tag.xarg.type=="image/svg+xml" then + return true + else + return false, "only 'image/svg+xml' is allowed for 'type'" + end + end + } +} + +----------------------------------------------------------------------------- +-- Specifies which attributes are allowed for _all_ tags. This table should +-- probably be limited to "class", "alt" and "title". +----------------------------------------------------------------------------- +GENERIC_ATTRIBUTES = { + class = ".", + alt = ".", + title = ".", +} + +----------------------------------------------------------------------------- +-- The class table for the XSS Filter. +----------------------------------------------------------------------------- + +local XSSFilter = {} +local XSSFilter_mt = {__metatable = {}, __index = XSSFilter} + +----------------------------------------------------------------------------- +-- Creates a new instance of XSSFilter. +-- +-- @param allowed_tags [optional] a table specifying which tags are allowed +-- (defaults to ALLOWED_TAGS). +-- @param generic_attrs [optional] a table specifying generic attributes +-- (defaults to GENERIC_ATTRIBUTES). +-- @return a new instance of XSSFilter. +----------------------------------------------------------------------------- +function new(allowed_tags, generic_attrs, tags_handler) + local obj = setmetatable({}, XSSFilter_mt) + obj:init(allowed_tags) + if iconv_loaded then + obj.utf8_converter = iconv.new("UTF8", "UTF8") + end + obj.tags_handler = tags_handler or REPLACE_TAGS + + return obj +end + +----------------------------------------------------------------------------- +-- Initializes the new instance of XSSFilter. This function is called by +-- new() and does not need to be called by the client. +-- +-- @param allowed_tags [optional] a table specifying which tags are allowed +-- (defaults to ALLOWED_TAGS). +-- @param generic_attrs [optional] a table specifying generic attributes +-- (defaults to GENERIC_ATTRIBUTES). +----------------------------------------------------------------------------- +function XSSFilter:init(allowed_tags, generic_attrs) + self.allowed_tags = allowed_tags or ALLOWED_TAGS + for i,v in ipairs(self.allowed_tags) do + self.allowed_tags[v] = self.allowed_tags[v] or {} + end + self.generic_attributes = generic_attrs or GENERIC_ATTRIBUTES +end + +----------------------------------------------------------------------------- +-- Parses simple XML. Adapted from from Roberto Ierusalmischy's collect() +-- (see http://lua-users.org/wiki/LuaXml). +-- +-- @param xml XML as a string. +-- @return A table representing the tags. +----------------------------------------------------------------------------- +local function parse_xml(s) + + --- An auxiliary function to parse tag's attributes + local function parse_attributes(s) + local arg = {} + string.gsub(s, "(%w+)=([\"'])(.-)%2", function (w, _, a) + arg[w] = a + end) + return arg + end + + local stack = {} + local top = {} + table.insert(stack, top) + local ni,c,label,xarg, empty + local i, j = 1, 1 + while true do + ni,j,c,label,xarg, empty = string.find(s, "<(%/?)(%w+)(.-)(%/?)>", i) + if not ni then break end + local text = string.sub(s, i, ni-1) + --if not string.find(text, "^%s*$") then + table.insert(top, text) + --end + if empty == "/" then -- empty element tag + table.insert(top, {label=label, xarg=parse_attributes(xarg), empty=1}) + elseif c == "" then -- start tag + top = {label=label, xarg=parse_attributes(xarg)} + table.insert(stack, top) -- new level + else -- end tag + local toclose = table.remove(stack) -- remove top + top = stack[#stack] + if #stack < 1 then + error("nothing to close with "..label) + end + if toclose.label ~= label then + error("trying to close "..toclose.label.." with "..label) + end + table.insert(top, toclose) + end + i = j+1 + end + local text = string.sub(s, i) + if not string.find(text, "^%s*$") then + table.insert(stack[stack.n], text) + end + if #stack > 1 then + error("unclosed "..stack[stack.n].label) + end + return stack[1] +end + +--x-------------------------------------------------------------------------- +-- An auxiliary function to match a value against a list of patterns. +----------------------------------------------------------------------------- +local function find_match(value, patterns) + patterns = patterns or {} + if type(patterns) == "string" then patterns = {patterns} end + for _, pattern in ipairs(patterns) do + if value:find(pattern) then return true end + end +end + +local function dummy_test() + return true +end + +----------------------------------------------------------------------------- +-- Filters (X)HTML. The input must be valid XML, without doctype and +-- document element. It doesn't not need to be specifically XHTML 1.x, in +-- the sense that no specific schema is expected. +-- +-- @param html An HTML string that must parse as valid XML if +-- is appended to it on both sides. +-- @return A string with all but the allowed tags removed. +----------------------------------------------------------------------------- +function XSSFilter:filter(html) + + if self.utf8_converter then + out, err = self.utf8_converter:iconv(html) + if err then + html = "[Invalid UTF8 - removed by XSSFilter]" + end + end + + local status, parsed = pcall(parse_xml, ""..html.."") + if not status then + return nil, "XSSFilter could not parse (X)HTML:\n"..html:gsub("<", "<"):gsub(">", ">") + end + + local buffer = "" + + -- this function is called recursively on all nodes + function xml2string(t) + for i,child in ipairs(t) do + if type(child) == "string" then + buffer = buffer..child + elseif type(child) == "table" then + local taginfo = self.allowed_tags[child.label] + if not taginfo then + buffer = buffer..self:tags_handler(child.label, "not allowed", child[1]) + else + local test_result, why_not = (taginfo._test or dummy_test)(child) + if not test_result then + buffer = buffer..self:tags_handler(child.label, why_not, child[1]) + else + -- ok, let's put the tag in + -- we might still strip some attributes, but silently + buffer = buffer.."<"..child.label + for attr, value in pairs(child.xarg) do + local patterns = taginfo[attr] or self.generic_attributes[attr] or {} + if find_match(value, patterns) then + buffer = buffer.." "..attr..'="'..value:gsub('"', """)..'"' + end + end + if child.empty then + buffer = buffer.." />" + else + buffer = buffer..">" + xml2string(child) + buffer = buffer.."" + end + end + end + else + error("XSSFilter: Unexpected type of field in parsed XML") + end + end + end + -- call the xml2string() function on the first top node. + xml2string(parsed[2]) + return buffer +end + +----------------------------------------------------------------------------- +-- Call handler to get text for replacing bad tags. +-- +-- @param tag tag name. +-- @param message [optional] an explanation for why the tag was removed. +-- @param text source text without tags +-- @return replacement HTML. +----------------------------------------------------------------------------- +function XSSFilter:call_tags_handler(tag, message, text) + local res, err = self:tags_handler(tag, message, text) + if not res then + error(err) + end + return res or "" +end \ No newline at end of file diff --git a/OpenWrt命令记录.txt b/OpenWrt命令记录.txt new file mode 100644 index 0000000..72f3823 --- /dev/null +++ b/OpenWrt命令记录.txt @@ -0,0 +1,114 @@ +openwrt配置命令: + +sudo minicom -D /dev/ttyUSB0 +la /dev/ttyUSB0 + + +cd /etc/config/ +cat wireless +/etc/init.d/network reload +cat /etc/config/wireless +router_reset +ifconfig + +r13: http://192.168.232.1 + +r10: http://192.168.233.1 + +亮哥邮箱: zhengkl@meizu.com + +ssh root@192.168.232.1 + +局域网地址:10.2.64.20 + +-------------------------------------------------------- + + +git配置命令: + + +git branch -a + +git checkout 切换分支 + +git log + +git head + +git diff + +---------------------------------------------------------- +gvim字体设置: +DejaVu Sans Mono 12 + +中继器配置: +cd /usr/bin/apcli_connect.sh + +--------------------------------------------------------- + + +如何通过Luci界面找到相应函数代码并修改? +(1)浏览器找到相关函数名 +(2)在luci/controller/admin/network.lua中找到相应函数 +(3)看懂,修改 +(4)移植到r13的api/index.lua或meizu目录的相应lua文件中 +(5)./build.sh + +修改 / 编译: +/usr/lib/lua/meizu/build.sh +logread -f 查看日志 +----------------------------------------------------------- + +小米luci代码路径: + +/home/rh/my_code/miwifi_package_rom/usr/lib/lua/luci + + +小米功能基础库路径: +/home/rh/my_code/miwifi_package_rom/usr/lib/lua/xiaoqiang + + + +定时wifi开关(智能开关)路径: + +/home/rh/my_code/miwifi_package_rom/usr/lib/lua/luci/controller/api/xqsystem.lua --line 1517 + +/home/rh/my_code/miwifi_package_rom/usr/lib/lua/xiaoqiang/common/XQFunction.lua --line 89 & 29 + + + +------------------------------------------------------------ +grep: +grep -F "bind_router" ./* -Rn --查找正文中含有bind_router的关键字 +-F 匹配字符串 +-R 递归匹配 +-n 显示行号 + + +ctags: + +使用ctags: ctags -R ./* + +ctrl + ] 查找具体函数 +ctrl + t 返回调用函数处 + +taglist: +:Tlist 打开Taglist + +------------------------------------------------------------ +传文件到U盘:/media + + + + + + + + + + + + + + +