admin.cgi 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518
  1. #!/bin/bash
  2. _WWW='/www'
  3. _PWDFILE="/opt/lib/htpasswd"
  4. _TMP="${_WWW}/tmp"
  5. _LOG="${_WWW}/log/admin.log"
  6. _DEBUG=1
  7. ## logging
  8. logThis() {
  9. [[ $_DEBUG -gt 0 ]] || return 0
  10. [[ $_ERR -gt 0 ]] && _TYPE='E:' || _TYPE='I:' ## Info or Error indication
  11. local _TIME=$(printf '%(%d.%m.%Y %H:%M:%S)T' -1)
  12. printf '%b\n' "$_TIME $_TYPE ${1} " >> $_LOG
  13. [[ $_DEBUG -gt 1 ]] && printf '%b\n' "[verbose] $_TYPE ${1}"
  14. return 0
  15. }
  16. ## http response
  17. headerPrint() {
  18. case ${1} in
  19. 200) printf '%b' 'HTTP/1.1 200 OK\nAccess-Control-Allow-Origin: *\n\n';;
  20. 301) printf '%b' "HTTP/1.1 301 Moved Permanently\nLocation: ${HTTP_REFERER}\n\n";;
  21. 403) printf '%b' 'HTTP/1.1 403 Forbidden\n\n';;
  22. 405) printf '%b' 'HTTP/1.1 405 Method Not Allowed\n\n';;
  23. 406) printf '%b' 'HTTP/1.1 406 Not Acceptable\n\n';;
  24. esac
  25. return 0
  26. }
  27. htDigest() {
  28. _USER='admin'
  29. _PWD=$1
  30. _REALM='superglue'
  31. _HASH=$(echo -n "$_USER:$_REALM:$_PWD" | md5sum | cut -b -32)
  32. echo -n "$_USER:$_REALM:$_HASH"
  33. }
  34. urlDec() {
  35. local value=${*//+/%20}
  36. for part in ${value//%/ \\x}; do
  37. printf "%b%s" "${part:0:4}" "${part:4}"
  38. done
  39. }
  40. setQueryVars() {
  41. local _POST=$(cat)
  42. local vars=${_POST//\*/%2A}
  43. for var in ${vars//&/ }; do
  44. local value=$(urlDec "${var#*=}")
  45. value=${value//\\/\\\\}
  46. eval "_${var%=*}=\"${value//\"/\\\"}\""
  47. done
  48. }
  49. getQueryFile() {
  50. _POST_TMP=$(mktemp -p $_TMP) ## make tmp POST file
  51. cat > $_POST_TMP ## cautiously storing entire POST in a file
  52. logThis "'multipart': decoding stream"
  53. local _BND=$(findPostOpt 'boundary')
  54. ## bash is binary unsafe and eats away precious lines
  55. ## thus using gawk
  56. function cutFile() {
  57. gawk -v "want=$1" -v "bnd=$_BND" '
  58. BEGIN { RS="\r\n"; ORS="\r\n" }
  59. # reset based on boundaries
  60. $0 == "--"bnd"" { st=1; next; }
  61. $0 == "--"bnd"--" { st=0; next; }
  62. $0 == "--"bnd"--\r" { st=0; next; }
  63. # search for wanted file
  64. st == 1 && $0 ~ "^Content-Disposition:.* name=\""want"\"" { st=2; next; }
  65. st == 1 && $0 == "" { st=9; next; }
  66. # wait for newline, then start printing
  67. st == 2 && $0 == "" { st=3; next; }
  68. st == 3 { print $0 }
  69. ' 2>&1
  70. }
  71. cutFile 'fwupload' < "${_POST_TMP}" > "${_TMP}/fwupload.bin"
  72. }
  73. ## find arbitrary option supplied in Content-Type header
  74. ## eg: "Content-Type:application/octet-stream; verbose=1"
  75. findPostOpt() {
  76. for i in "${CONTENT_TYPE[@]:1}"; do
  77. case "${i/=*}" in
  78. "$1") printf '%b' "${i/*=}" ;;
  79. esac
  80. done
  81. return 0
  82. }
  83. runSuid() {
  84. local _SID=$(/usr/bin/ps -p $$ -o sid=) ## pass session id to the child
  85. local _CMD=$@
  86. sudo ./suid.sh $_CMD $_SID 2>/dev/null
  87. }
  88. validIp() {
  89. local _IP=$1
  90. local _RET=1
  91. if [[ $_IP =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
  92. OIFS=$IFS
  93. IFS='.'
  94. _IP=($_IP)
  95. IFS=$OIFS
  96. [[ ${_IP[0]} -le 255 && ${_IP[1]} -le 255 && ${_IP[2]} -le 255 && ${_IP[3]} -le 255 ]]
  97. _RET=$?
  98. fi
  99. return $_RET
  100. }
  101. pwdChange() {
  102. if [[ ! -z "${_pwd##$_pwdd}" ]]; then
  103. _ERR=1
  104. showMesg 'Passwords did not match'
  105. fi
  106. if [[ ${#_pwd} -lt 6 ]]; then
  107. _ERR=1
  108. showMesg 'Password must be at least 6 characters long'
  109. fi
  110. runSuid "echo -e \"$_pwd\n$_pwd\" | passwd root"
  111. runSuid "echo $(htDigest $_pwd) > $_PWDFILE"
  112. _ERR=$?
  113. if [[ $_ERR -gt 0 ]]; then
  114. showMesg 'Password change failed'
  115. else
  116. showMesg 'Password is changed'
  117. fi
  118. }
  119. lanAddr() {
  120. logThis "new LAN addr is: $_laddr"
  121. validIp $_laddr || showMesg 'Not valid network address'
  122. doUci set laddr $_laddr
  123. _ERR=$?
  124. if [[ $_ERR -gt 0 ]]; then
  125. showMesg 'Setting network address failed'
  126. else
  127. (sleep 1; doUci commit network; doUci commit wireless;)&
  128. showMesg 'New network address is set' "Your server is now accessible under <a href='http://superglue.local/admin'>http://superglue.local/admin</a>"
  129. fi
  130. }
  131. wanSet() {
  132. if [[ ! -z $_wanifname ]]; then
  133. ## eth and wlan wan cases are different!
  134. ## eth wan requires:
  135. ## config interface 'wan'
  136. ## option ifname 'eth0'
  137. ##
  138. ## config wifi-iface
  139. ## option device 'radio0'
  140. ## option network 'wan'
  141. ## option disabled '1' (or no 'config wifi-iface' section at all)
  142. ##
  143. ## wlan wan requires:
  144. ## config interface 'wan'
  145. ## option proto 'dhcp'
  146. ## (without 'option ifname' specified!)
  147. ##
  148. ## config wifi-iface
  149. ## option device 'radio0'
  150. ## option network 'wan'
  151. logThis "wan.ifname=$_wanifname"
  152. if [[ $_wanifname == 'eth0' ]]; then
  153. doUci set wanifname $_wanifname
  154. doUci set wanwifacedis '1'
  155. elif [[ $_wanifname == 'wlan1' ]]; then
  156. doUci set wanifname ''
  157. doUci set wanwifacedis ''
  158. fi
  159. if [[ $_wanproto == 'dhcp' ]]; then
  160. doUci set wanproto dhcp
  161. elif [[ $_wanproto == 'static' ]]; then
  162. logThis "wan.ipaddr=$_wanipaddr"
  163. doUci set wanproto static
  164. doUci set wanipaddr $_wanipaddr
  165. doUci set wannetmask $_wannetmask
  166. fi
  167. if [[ $_wanifname == 'wlan1' ]]; then
  168. ssidChange || showMesg 'Wireless changes failed'
  169. fi
  170. ## background the following
  171. doUci commit network &&
  172. showMesg 'Internet connection is configured' 'Waiting for device to get ready' ||
  173. showMesg 'Configuring Internet connection failed'
  174. fi
  175. logThis "new WAN iface is: $_wanifname"
  176. }
  177. ssidChange() {
  178. ## check for iface
  179. [[ ! $_iface =~ ^('wan'|'lan')$ ]] && showMesg 'Error changing wireless settings' 'unknown/unconfigured interface'
  180. logThis "$_iface is being set"
  181. _p=$_iface
  182. ## default enc for now
  183. local _enc='psk2'
  184. if [[ $_iface == 'wan' ]]; then
  185. local _mode='sta'
  186. local _ssid="${_wanssid}"
  187. local _key="${_wankey}"
  188. else
  189. local _mode='ap'
  190. local _ssid="${_lanssid}"
  191. local _key="${_lankey}"
  192. fi
  193. logThis "ssid: $_ssid [$_mode], key: $_key [$_enc]"
  194. logThis $_wanssid
  195. if [[ ${#_ssid} -lt 4 ]]; then
  196. _ERR=1
  197. showMesg 'SSID must be at least 4 characters long'
  198. fi
  199. doUci set $_p'ssid' "${_ssid}"
  200. _ERR=$?
  201. [[ $_ERR -gt 0 ]] && showMesg 'New SSID is not set'
  202. if [[ -z $_key ]]; then
  203. ## if key is empty set encryption to none and remove key
  204. doUci set $_p'key' && doUci set $_p'enc' 'none'
  205. _ERR=$?
  206. else
  207. if [[ ${#_key} -lt 8 ]]; then
  208. _ERR=1
  209. showMesg 'Passphrase must be at least 8 characters long'
  210. fi
  211. doUci set $_p'key' "${_key}" && doUci set $_p'enc' "${_enc}"
  212. _ERR=$?
  213. [[ $_ERR -gt 0 ]] && showMesg 'Passphrase is not set'
  214. fi
  215. [[ $_ERR -gt 0 ]] && return $_ERR ##showMesg 'Wireless changes failed'
  216. doUci commit wireless ##&& showMesg 'Wireless changes applied'
  217. }
  218. #showError() {
  219. # headerPrint 406
  220. # logThis "$@"
  221. # echo "ERROR: $@"
  222. # exit 1
  223. #}
  224. showMesg() {
  225. logThis "$@"
  226. local _MSG=$1
  227. local _SUBMSG=$2
  228. _MSG=${_MSG:='Not defined'}
  229. _SUBMSG=${_SUBMSG:='back to control panel in a second..'}
  230. if [[ $_ERR -gt 0 ]]; then
  231. local _TYPE='ERROR: '
  232. headerPrint 406
  233. else
  234. local _TYPE='OK: '
  235. headerPrint 200
  236. fi
  237. htmlHead "<meta http-equiv='refresh' content='3;url=${HTTP_REFERER}'>"
  238. echo "<body>
  239. <h1>SG</h1>
  240. <hr>
  241. <h2 style='display:inline'>$_TYPE $_MSG</h2>
  242. <span style='display:inline; margin-left: 50px;'>$_SUBMSG</span>
  243. <hr>
  244. </body></html>"
  245. exit 0
  246. }
  247. updateFw() {
  248. logThis "updating fw"
  249. _FWFILE="${_TMP}/fwupload.bin"
  250. logThis "fwfile is: $(ls -lad $_FWFILE)"
  251. _OUT="$(/sbin/sysupgrade -T $_FWFILE 2>&1)"
  252. _ERR=$?
  253. [[ $_ERR -gt 0 ]] && showMesg "$_OUT"
  254. _OUT="$(runSuid /sbin/mtd -e firmware -q write $_FWFILE firmware)"
  255. _ERR=$?
  256. [[ $_ERR -gt 0 ]] && showMesg "mtd failed, $_OUT"
  257. runSuid reboot
  258. showMesg 'Firmware update is completed, rebooting..' 'this might take up to 60 seconds'
  259. }
  260. rebootNow() {
  261. logThis "reboot: now!"
  262. runSuid reboot
  263. showMesg 'Rebooting..' 'this might take up to 60 seconds'
  264. }
  265. doUci() {
  266. local _CMD=''
  267. local _ARG=''
  268. case $1 in
  269. get|set|commit) _CMD=$1;;
  270. *) logThis 'bad UCI command'; headerPrint 405; echo 'bad UCI command'; exit 1 ;;
  271. esac
  272. case $2 in
  273. lanssid) _ARG='wireless.@wifi-iface[0].ssid';;
  274. lanenc) _ARG='wireless.@wifi-iface[0].encryption';;
  275. lankey) _ARG='wireless.@wifi-iface[0].key';;
  276. lanipaddr) _ARG='network.lan.ipaddr';;
  277. wanifname) _ARG='network.wan.ifname';;
  278. wanproto) _ARG='network.wan.proto';;
  279. wanipaddr) _ARG='network.wan.ipaddr';;
  280. wannetmask) _ARG='network.wan.netmask';;
  281. wanwifacedis) _ARG='wireless.@wifi-iface[1].disabled';;
  282. wanssid) _ARG='wireless.@wifi-iface[1].ssid';;
  283. wanenc) _ARG='wireless.@wifi-iface[1].encryption';;
  284. wankey) _ARG='wireless.@wifi-iface[1].key';;
  285. *) if [[ $_CMD == 'commit' ]]; then
  286. _ARG=$2
  287. else
  288. logThis "bad UCI entry: $2"
  289. _ERR=1
  290. showMesg 'bad UCI entry'
  291. fi ;;
  292. esac
  293. if [[ $_CMD == 'get' ]]; then
  294. if [ ! -z $_ARG ]; then
  295. /sbin/uci -q get $_ARG || return $?
  296. fi
  297. fi
  298. if [[ $_CMD == 'set' ]]; then
  299. local _VAL=$3
  300. if [ -z $_VAL ]; then
  301. logThis "empty $_ARG value, removing record"
  302. runSuid /sbin/uci delete $_ARG || ( echo "uci delete $_ARG: error"; exit 1; )
  303. fi
  304. if [ ! -z $_ARG ]; then
  305. logThis "setting $_ARG value"
  306. runSuid /sbin/uci set $_ARG=$_VAL || ( echo "uci set $_ARG: error"; exit 1; )
  307. fi
  308. fi
  309. if [[ $_CMD == 'commit' ]]; then
  310. runSuid /sbin/uci commit $_ARG|| echo "uci commit $_ARG: error"
  311. if [[ "$_ARG" == 'wireless' ]]; then
  312. runSuid /sbin/wifi || echo 'wifi: error'
  313. fi
  314. if [[ "$_ARG" == 'network' ]]; then
  315. runSuid /etc/init.d/dnsmasq restart && runSuid /etc/init.d/network restart || echo 'network: error'
  316. fi
  317. fi
  318. }
  319. getStat() {
  320. . /usr/share/libubox/jshn.sh
  321. local _IFACE=$1
  322. local _IFSTAT=$(runSuid ubus call network.interface.wan status 2>/dev/null)
  323. logThis "$_IFSTAT"
  324. json_get_type _IFSTAT ipv4_address
  325. if json_get_type _IFSTAT ipv4_address && [[ "$_IFSTAT" == 'array' ]]; then
  326. json_select ipv4_address
  327. json_get_type _IFSTAT 1
  328. if [[ "$_IFSTAT" == 'object' ]]; then
  329. json_select 1
  330. json_get_var IP4 address
  331. json_get_var Subnet4 mask
  332. [[ "$IP4" != '' ]] && [[ "$Subnet4" != '' ]] && IP4="$IP4/$Subnet4"
  333. fi
  334. fi
  335. logThis $IP4
  336. }
  337. ##getStat wan
  338. htmlHead() {
  339. echo "<!-- obnoxious code below, keep your ports tight -->
  340. <!doctype html>
  341. <html>
  342. <head><title>SuperGlue | Administration</title>
  343. $@
  344. <link rel='stylesheet' type='text/css' href='http://${HTTP_HOST}/resources/admin/admin.css'>
  345. </head>"
  346. }
  347. if [[ $REQUEST_METHOD == 'POST' ]]; then
  348. if [[ $CONTENT_LENGTH -gt 0 ]]; then
  349. CONTENT_TYPE=( ${CONTENT_TYPE} )
  350. _CONTENT_TYPE="${CONTENT_TYPE[0]/;}"
  351. _ENC="${HTTP_CONTENT_ENCODING}"
  352. case "${_CONTENT_TYPE}" in
  353. application/x-www-form-urlencoded) setQueryVars;;
  354. multipart/form-data) getQueryFile;;
  355. *) _ERR=1; _OUT='this is not a post' ;;
  356. esac
  357. case $REQUEST_URI in
  358. /admin/pwdchange) pwdChange;;
  359. /admin/ssidchange) ssidChange;;
  360. /admin/lanaddr) lanAddr;;
  361. /admin/updatefw) updateFw;;
  362. /admin/rebootnow) rebootNow;;
  363. /admin/wan) wanSet;;
  364. *) logThis 'bad action'; headerPrint 405; echo 'no such thing'; exit 1;;
  365. esac
  366. fi
  367. headerPrint 301
  368. fi
  369. headerPrint 200
  370. ## html head
  371. htmlHead
  372. sgver=$(cat /etc/superglue_version)
  373. devmod=$(cat /etc/superglue_model)
  374. openwrt=$(cat /etc/openwrt_version)
  375. wanifname=$(doUci get wanifname || echo 'wlan0') ## TODO fix this
  376. wanproto=$(doUci get wanproto)
  377. wanipaddr=$(doUci get wanipaddr)
  378. wannetmask=$(doUci get wannetmask)
  379. wanssid=$(doUci get wanssid)
  380. wankey=$(doUci get wankey)
  381. echo "<body>
  382. <h1>SG</h1>
  383. <hr>
  384. <h2 style='display:inline'>Superglue server control panel</h2>
  385. <span style='display:block;'>System version: $sgver | Device: $devmod | OpenWRT: $openwrt</span>
  386. <span style='display:block;'>$(uptime)</span>
  387. <hr>
  388. Update firmware:
  389. <form method='post' action='/admin/updatefw' enctype='multipart/form-data'>
  390. <div id='uploadbox'>
  391. <input id='uploadfile' placeholder='Choose file' disabled='disabled'>
  392. <input id='uploadbtn' name='fwupload' type='file'>
  393. </div>
  394. <input type='submit' value='Upload'>
  395. </form>
  396. <hr>
  397. Internet connection:
  398. <form method='post' action='/admin/wan' name='wan' onchange='formChange();'>
  399. <div style='display:inline-flex'>
  400. <div style='display:inline-block;'>
  401. <select name='wanifname' id='wanifname' style='display:block'>
  402. <option value='eth0' id='eth' $([[ $wanifname =~ ('eth') ]] && echo 'selected')>Wired (WAN port)</option>
  403. <option value='wlan1' id='wlan' $([[ $wanifname =~ ('wlan') ]] && echo 'selected')>Wireless (Wi-Fi)</option>
  404. </select>
  405. <fieldset id='wanwifi' class='hide'>
  406. <input type='text' name='wanssid' value='$wanssid'>
  407. <input type='password' name='wankey' value='$wankey'>
  408. </fieldset>
  409. </div>
  410. <div style='display:inline-block;'>
  411. <select name='wanproto' id='wanproto' style='display:block'>
  412. <option value='dhcp' name='dhcp' id='dhcp' $([[ $wanproto == 'dhcp' ]] && echo 'selected')>Automatic (DHCP)</option>
  413. <option value='stat' name='dhcp' id='stat' $([[ $wanproto == 'static' ]] && echo 'selected')>Manual (Static IP)</option>
  414. </select>
  415. <fieldset id='wanaddr' class='hide' >
  416. <input type='text' name='wanipaddr' id='wanipaddr' value='$wanipaddr'>
  417. <input type='text' name='wangw' id='wannetmask' value='$wannetmask'>
  418. </fieldset>
  419. </div>
  420. </div>
  421. <input type='hidden' name='iface' value='wan' class='inline'>
  422. <input type='submit' value='Apply'>
  423. </form>
  424. <hr>
  425. Local wireless network:
  426. <form method='post' action='/admin/ssidchange'>
  427. <div style='display:inline-flex'>
  428. <div style='display:inline-block;'>
  429. <input type='text' name='lanssid' value='$(doUci get lanssid)'>
  430. <input type='password' name='lankey' value='$(doUci get lankey)'>
  431. </div>
  432. <div style='display:inline-block;'>
  433. <input type='text' name='lanipaddr' value='$(doUci get lanipaddr)'>
  434. <input type='hidden' name='iface' value='lan' class='inline'>
  435. </div>
  436. </div>
  437. <input type='submit' value='Apply'>
  438. </form>
  439. <hr>
  440. <form action='/admin/rebootnow' method='post' class='inline'>
  441. <input type='hidden' name='reboot' value='now' class='inline'>
  442. <input type='submit' value='Reboot' class='inline'>
  443. </form>
  444. <form action='http://logout@${HTTP_HOST}/admin' method='get' class='inline'>
  445. <input type='submit' value='Logout' class='inline'>
  446. </form>
  447. <hr>
  448. Memory:
  449. <pre>$(free)</pre>
  450. <hr>
  451. Storage:
  452. <pre>$(df -h)</pre>
  453. <hr>
  454. Environment:
  455. <pre>$(env)</pre>
  456. <hr>
  457. $_POST
  458. </body>
  459. <script type='text/javascript' src='http://${HTTP_HOST}/resources/admin/admin.js'></script>
  460. </html>"
  461. exit 0