admin.sh 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. #!/bin/bash
  2. _PWDFILE='/www/lib/admin/htpasswd'
  3. _TMP='/tmp'
  4. _LOG="${_TMP}/admin.log"
  5. _DEBUG=1
  6. ## logging
  7. logThis() {
  8. [[ $_DEBUG -gt 0 ]] || return 0
  9. [[ $_ERR -gt 0 ]] && _TYPE='E:' || _TYPE='I:' ## Info or Error indication
  10. local _TIME=$(printf '%(%d.%m.%Y %H:%M:%S)T' -1)
  11. printf '%b\n' "$_TIME $_TYPE ${1} " >> $_LOG
  12. [[ $_DEBUG -gt 1 ]] && printf '%b\n' "[verbose] $_TYPE ${1}"
  13. return 0
  14. }
  15. ## http response
  16. headerPrint() {
  17. case ${1} in
  18. 200) printf '%b' 'HTTP/1.1 200 OK\nAccess-Control-Allow-Origin: *\n\n';;
  19. 301) printf '%b' "HTTP/1.1 301 Moved Permanently\nLocation: ${HTTP_REFERER}\n\n";;
  20. 403) printf '%b' 'HTTP/1.1 403 Forbidden\n\n';;
  21. 405) printf '%b' 'HTTP/1.1 405 Method Not Allowed\n\n';;
  22. 406) printf '%b' 'HTTP/1.1 406 Not Acceptable\n\n';;
  23. esac
  24. return 0
  25. }
  26. htDigest() {
  27. _USER='admin'
  28. _PWD=$1
  29. _REALM='superglue'
  30. _HASH=$(echo -n "$_USER:$_REALM:$_PWD" | md5sum | cut -b -32)
  31. echo -n "$_USER:$_REALM:$_HASH"
  32. }
  33. urlDec() {
  34. local value=${*//+/%20}
  35. for part in ${value//%/ \\x}; do
  36. printf "%b%s" "${part:0:4}" "${part:4}"
  37. done
  38. }
  39. setQueryVars() {
  40. local _POST=$(cat)
  41. local vars=${_POST//\*/%2A}
  42. for var in ${vars//&/ }; do
  43. local value=$(urlDec "${var#*=}")
  44. value=${value//\\/\\\\}
  45. eval "_${var%=*}=\"${value//\"/\\\"}\""
  46. done
  47. }
  48. getQueryFile() {
  49. _POST_TMP=$(mktemp -p $_TMP) ## make tmp POST file
  50. cat > $_POST_TMP ## cautiously storing entire POST in a file
  51. logThis "'multipart': decoding stream"
  52. local _BND=$(findPostOpt 'boundary')
  53. ## bash is binary unsafe and eats away precious lines
  54. ## thus using gawk
  55. function cutFile() {
  56. gawk -v "want=$1" -v "bnd=$_BND" '
  57. BEGIN { RS="\r\n"; ORS="\r\n" }
  58. # reset based on boundaries
  59. $0 == "--"bnd"" { st=1; next; }
  60. $0 == "--"bnd"--" { st=0; next; }
  61. $0 == "--"bnd"--\r" { st=0; next; }
  62. # search for wanted file
  63. st == 1 && $0 ~ "^Content-Disposition:.* name=\""want"\"" { st=2; next; }
  64. st == 1 && $0 == "" { st=9; next; }
  65. # wait for newline, then start printing
  66. st == 2 && $0 == "" { st=3; next; }
  67. st == 3 { print $0 }
  68. ' 2>&1
  69. }
  70. cutFile 'fwupload' < "${_POST_TMP}" > "${_TMP}/fwupload.bin"
  71. }
  72. ## find arbitrary option supplied in Content-Type header
  73. ## eg: "Content-Type:application/octet-stream; verbose=1"
  74. findPostOpt() {
  75. for i in "${CONTENT_TYPE[@]:1}"; do
  76. case "${i/=*}" in
  77. "$1") printf '%b' "${i/*=}" ;;
  78. esac
  79. done
  80. return 0
  81. }
  82. runSuid() {
  83. local _SID=$(ps -p $$ -o sid=) ## pass session id to the child
  84. local _CMD=$@
  85. sudo ./suid.sh $_CMD $_SID 2>&1
  86. }
  87. pwdChange() {
  88. if [[ ! -z "${_pwd##$_pwdd}" ]]; then
  89. _ERR=1
  90. showMesg 'Passwords did not match'
  91. fi
  92. if [[ ${#_pwd} -lt 6 ]]; then
  93. _ERR=1
  94. showMesg 'Password must be at least 6 characters long'
  95. fi
  96. runSuid "echo -e \"$_pwd\n$_pwd\" | passwd root"
  97. runSuid "echo $(htDigest $_pwd) > $_PWDFILE"
  98. _ERR=$?
  99. if [[ $_ERR -gt 0 ]]; then
  100. showMesg 'Password change failed'
  101. else
  102. showMesg 'Password is changed'
  103. fi
  104. }
  105. ssidChange() {
  106. ## default enc for now
  107. local _enc='psk2'
  108. logThis "new ssid is: $_ssid"
  109. logThis "new key is: $_key"
  110. if [[ $_ssid != $_pssid ]]; then
  111. if [[ ${#_ssid} -lt 4 ]]; then
  112. _ERR=1
  113. showMesg 'SSID must be at least 4 characters long'
  114. fi
  115. setUci ssid $_ssid
  116. _ERR=$?
  117. [[ $_ERR -gt 0 ]] && showMesg 'New SSID is not set'
  118. fi
  119. if [[ $_key != $_pkey ]]; then
  120. if [[ -z $_key ]]; then
  121. ## if key is empty set encryption to none and remove key
  122. setUci key && setUci enc 'none'
  123. _ERR=$?
  124. else
  125. if [[ ${#_key} -lt 8 ]]; then
  126. _ERR=1
  127. showMesg 'Passphrase must be at least 8 characters long'
  128. fi
  129. setUci key $_key && setUci enc $_enc
  130. _ERR=$?
  131. [[ $_ERR -gt 0 ]] && showMesg 'Passphrase is not set'
  132. fi
  133. fi
  134. [[ $_ERR -gt 0 ]] && showMesg 'Wireless changes failed'
  135. commitUci && showMesg 'Wireless changes applied'
  136. }
  137. #showError() {
  138. # headerPrint 406
  139. # logThis "$@"
  140. # echo "ERROR: $@"
  141. # exit 1
  142. #}
  143. showMesg() {
  144. logThis "$@"
  145. local _MSG=$1
  146. local _SUBMSG=$2
  147. _MSG=${_MSG:='Not defined'}
  148. _SUBMSG=${_SUBMSG:='back to control panel in a second..'}
  149. if [[ $_ERR -gt 0 ]]; then
  150. local _TYPE='ERROR: '
  151. headerPrint 406
  152. else
  153. local _TYPE='OK: '
  154. headerPrint 200
  155. fi
  156. htmlHead "<meta http-equiv='refresh' content='5;url=${HTTP_REFERER}'>"
  157. echo "<body>
  158. <img src='/resources/default/img/placeholder.png' class='logo'>
  159. <hr>
  160. <h2 style='display:inline'>$_TYPE $_MSG</h2>
  161. <span style='display:inline; margin-left: 50px;'>$_SUBMSG</span>"
  162. if [[ $_ERR -eq 0 ]]; then
  163. echo "<form action=${HTTP_REFERER} method='get'><input type='submit' value='Back'></form>"
  164. fi
  165. echo "<hr>
  166. </body></html>"
  167. exit 0
  168. }
  169. updateFw() {
  170. logThis "updating fw"
  171. _FWFILE="${_TMP}/fwupload.bin"
  172. logThis "fwfile is: $(ls -lad $_FWFILE)"
  173. _OUT="$(/sbin/sysupgrade -T $_FWFILE 2>&1)"
  174. _ERR=$?
  175. [[ $_ERR -gt 0 ]] && showMesg "$_OUT"
  176. _OUT="$(runSuid /sbin/mtd -q write $_FWFILE firmware)"
  177. _ERR=$?
  178. [[ $_ERR -gt 0 ]] && showMesg "mtd failed, $_OUT"
  179. showMesg 'Firmware update is completed, rebooting..' 'this might take up to 60 seconds'
  180. runSuid reboot
  181. }
  182. rebootNow() {
  183. logThis "reboot: now!"
  184. showMesg 'Rebooting..' 'this might take up to 60 seconds'
  185. runSuid reboot
  186. }
  187. getUci() {
  188. local _ARG=''
  189. case $1 in
  190. ssid) _ARG='wireless.@wifi-iface[0].ssid';;
  191. enc) _ARG='wireless.@wifi-iface[0].encryption';;
  192. key) _ARG='wireless.@wifi-iface[0].key';;
  193. *) logThis 'bad uci command'; headerPrint 405; echo 'no such uci command'; exit 1;;
  194. esac
  195. if [ ! -z $_ARG ]; then
  196. /sbin/uci get $_ARG
  197. fi
  198. }
  199. setUci() {
  200. local _ARG=''
  201. case $1 in
  202. ssid) _ARG='wireless.@wifi-iface[0].ssid';;
  203. enc) _ARG='wireless.@wifi-iface[0].encryption';;
  204. key) _ARG='wireless.@wifi-iface[0].key';;
  205. *) logThis 'bad uci command'; headerPrint 405; echo 'no such uci command'; exit 1;;
  206. esac
  207. if [ -z $2 ]; then
  208. logThis "empty $_ARG value, removing record"
  209. runSuid /sbin/uci delete $_ARG || ( echo "uci delete $_ARG: error"; exit 1; )
  210. fi
  211. if [ ! -z $_ARG ]; then
  212. logThis "setting $_ARG value"
  213. runSuid /sbin/uci set $_ARG=$2 || ( echo "uci set $_ARG: error"; exit 1; )
  214. fi
  215. }
  216. commitUci() {
  217. runSuid /sbin/uci commit || echo "uci commit $_ARG: error"
  218. runSuid /sbin/wifi || echo 'wifi: error'
  219. }
  220. htmlHead() {
  221. echo "<!doctype html>
  222. <html>
  223. <head><title>SuperGlue | Administration</title>
  224. $@
  225. <script type='text/javascript' src='/resources/default/js/jquery.js'></script>
  226. <style>
  227. body { background:#ccc; color:#000; margin: 20px 0 0 200px; font-family: TitilliumWeb;}
  228. input { display: block; }
  229. .inline { display: inline; }
  230. img.logo { position: absolute; left:50px; top: 20px;}
  231. pre { white-space: pre-wrap; }
  232. @font-face { font-family: TitilliumWeb; src: url('/resources/default/fonts/Titillium_Web/TitilliumWeb-Regular.ttf') format('truetype'); }
  233. @font-face { font-family: TitilliumWeb; font-weight: bold; src: url('/resources/default/fonts/Titillium_Web/TitilliumWeb-Bold.ttf') format('truetype'); }
  234. </style>
  235. </head>"
  236. }
  237. ## unless auth is disabled in lighttpd
  238. ## it should never come to this,
  239. if [[ -z $HTTP_AUTHORIZATION ]]; then
  240. logThis 'no auth'
  241. headerPrint 403
  242. echo 'no is no'
  243. exit 1
  244. else logThis 'auth OK'
  245. fi
  246. if [[ $REQUEST_METHOD == 'POST' ]]; then
  247. if [[ $CONTENT_LENGTH -gt 0 ]]; then
  248. CONTENT_TYPE=( ${CONTENT_TYPE} )
  249. _CONTENT_TYPE="${CONTENT_TYPE[0]/;}"
  250. _ENC="${HTTP_CONTENT_ENCODING}"
  251. logThis $REQUEST_URI
  252. logThis $CONTENT_TYPE
  253. case "${_CONTENT_TYPE}" in
  254. application/x-www-form-urlencoded) setQueryVars;;
  255. multipart/form-data) getQueryFile;;
  256. *) _ERR=1; _OUT='this is not a post' ;;
  257. esac
  258. case $REQUEST_URI in
  259. /admin/pwdchange) pwdChange;;
  260. /admin/ssidchange) ssidChange;;
  261. /admin/updatefw) updateFw;;
  262. /admin/rebootnow) rebootNow;;
  263. *) logThis 'bad action'; headerPrint 405; echo 'no such thing'; exit 1;;
  264. esac
  265. fi
  266. headerPrint 301
  267. fi
  268. headerPrint 200
  269. ## html head
  270. htmlHead
  271. echo "<body>
  272. <img src='/resources/default/img/placeholder.png' class='logo'>
  273. <hr>
  274. <h2 style='display:inline'>Superglue server control panel</h2>
  275. <span style='display:inline; margin-left: 50px;'>$(uptime)</span>
  276. <hr>
  277. Change password:
  278. <form method='post' action='/admin/pwdchange'>
  279. <input type='text' name='usr' value='admin' readonly>
  280. <input type='password' name='pwd'>
  281. <input type='password' name='pwdd'>
  282. <input type='submit' value='Apply'>
  283. </form>
  284. <hr>
  285. Configure wireless network:
  286. <form method='post' action='/admin/ssidchange'>
  287. <input type='text' name='ssid' value='$(getUci ssid)'>
  288. <input type='text' name='key' value='$(getUci key)'>
  289. <input type='hidden' name='pssid' value='$(getUci ssid)'>
  290. <input type='hidden' name='pkey' value='$(getUci key)'>
  291. <input type='submit' value='Apply'>
  292. </form>
  293. <hr>
  294. Update firmware:
  295. <form method='post' action='/admin/updatefw' enctype='multipart/form-data'>
  296. <input type='file' name='fwupload'>
  297. <input type='submit' id='upload' value='Upload'>
  298. </form>
  299. <hr>
  300. <form action='/admin/rebootnow' method='post' class='inline'>
  301. <input type='hidden' name='reboot' value='now' class='inline'>
  302. <input type='submit' value='Reboot' class='inline'>
  303. </form>
  304. <form action='http://logout@${HTTP_HOST}/admin' method='get' class='inline'>
  305. <input type='submit' value='Logout' class='inline'>
  306. </form>
  307. <hr>
  308. $_POST
  309. Environment:
  310. <pre>$(env)</pre>
  311. Memory:
  312. <pre>$(free)</pre>
  313. Storage:
  314. <pre>$(df -h)</pre>
  315. </body></html>"
  316. exit 0