admin.sh 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  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. <hr>
  163. </body></html>"
  164. exit 0
  165. }
  166. updateFw() {
  167. logThis "updating fw"
  168. _FWFILE="${_TMP}/fwupload.bin"
  169. logThis "fwfile is: $(ls -lad $_FWFILE)"
  170. _OUT="$(/sbin/sysupgrade -T $_FWFILE 2>&1)"
  171. _ERR=$?
  172. [[ $_ERR -gt 0 ]] && showMesg "$_OUT"
  173. _OUT="$(runSuid /sbin/mtd -q write $_FWFILE firmware)"
  174. _ERR=$?
  175. [[ $_ERR -gt 0 ]] && showMesg "mtd failed, $_OUT"
  176. runSuid reboot
  177. showMesg 'Firmware update is completed, rebooting..' 'this might take up to 60 seconds'
  178. }
  179. rebootNow() {
  180. logThis "reboot: now!"
  181. runSuid reboot
  182. showMesg 'Rebooting..' 'this might take up to 60 seconds'
  183. }
  184. getUci() {
  185. local _ARG=''
  186. case $1 in
  187. ssid) _ARG='wireless.@wifi-iface[0].ssid';;
  188. enc) _ARG='wireless.@wifi-iface[0].encryption';;
  189. key) _ARG='wireless.@wifi-iface[0].key';;
  190. *) logThis 'bad uci command'; headerPrint 405; echo 'no such uci command'; exit 1;;
  191. esac
  192. if [ ! -z $_ARG ]; then
  193. /sbin/uci get $_ARG
  194. fi
  195. }
  196. setUci() {
  197. local _ARG=''
  198. case $1 in
  199. ssid) _ARG='wireless.@wifi-iface[0].ssid';;
  200. enc) _ARG='wireless.@wifi-iface[0].encryption';;
  201. key) _ARG='wireless.@wifi-iface[0].key';;
  202. *) logThis 'bad uci command'; headerPrint 405; echo 'no such uci command'; exit 1;;
  203. esac
  204. if [ -z $2 ]; then
  205. logThis "empty $_ARG value, removing record"
  206. runSuid /sbin/uci delete $_ARG || ( echo "uci delete $_ARG: error"; exit 1; )
  207. fi
  208. if [ ! -z $_ARG ]; then
  209. logThis "setting $_ARG value"
  210. runSuid /sbin/uci set $_ARG=$2 || ( echo "uci set $_ARG: error"; exit 1; )
  211. fi
  212. }
  213. commitUci() {
  214. runSuid /sbin/uci commit || echo "uci commit $_ARG: error"
  215. runSuid /sbin/wifi || echo 'wifi: error'
  216. }
  217. htmlHead() {
  218. echo "<!doctype html>
  219. <html>
  220. <head><title>SuperGlue | Administration</title>
  221. $@
  222. <script type='text/javascript' src='/resources/default/js/jquery.js'></script>
  223. <style>
  224. body { background:#ccc; color:#000; margin: 20px 0 0 200px; font-family: TitilliumWeb;}
  225. input { display: block; }
  226. .inline { display: inline; }
  227. img.logo { position: absolute; left:50px; top: 20px;}
  228. pre { white-space: pre-wrap; }
  229. @font-face { font-family: TitilliumWeb; src: url('/resources/default/fonts/Titillium_Web/TitilliumWeb-Regular.ttf') format('truetype'); }
  230. @font-face { font-family: TitilliumWeb; font-weight: bold; src: url('/resources/default/fonts/Titillium_Web/TitilliumWeb-Bold.ttf') format('truetype'); }
  231. </style>
  232. </head>"
  233. }
  234. ## unless auth is disabled in lighttpd
  235. ## it should never come to this,
  236. if [[ -z $HTTP_AUTHORIZATION ]]; then
  237. logThis 'no auth'
  238. headerPrint 403
  239. echo 'no is no'
  240. exit 1
  241. else logThis 'auth OK'
  242. fi
  243. if [[ $REQUEST_METHOD == 'POST' ]]; then
  244. if [[ $CONTENT_LENGTH -gt 0 ]]; then
  245. CONTENT_TYPE=( ${CONTENT_TYPE} )
  246. _CONTENT_TYPE="${CONTENT_TYPE[0]/;}"
  247. _ENC="${HTTP_CONTENT_ENCODING}"
  248. logThis $REQUEST_URI
  249. logThis $CONTENT_TYPE
  250. case "${_CONTENT_TYPE}" in
  251. application/x-www-form-urlencoded) setQueryVars;;
  252. multipart/form-data) getQueryFile;;
  253. *) _ERR=1; _OUT='this is not a post' ;;
  254. esac
  255. case $REQUEST_URI in
  256. /admin/pwdchange) pwdChange;;
  257. /admin/ssidchange) ssidChange;;
  258. /admin/updatefw) updateFw;;
  259. /admin/rebootnow) rebootNow;;
  260. *) logThis 'bad action'; headerPrint 405; echo 'no such thing'; exit 1;;
  261. esac
  262. fi
  263. headerPrint 301
  264. fi
  265. headerPrint 200
  266. ## html head
  267. htmlHead
  268. echo "<body>
  269. <img src='/resources/default/img/placeholder.png' class='logo'>
  270. <hr>
  271. <h2 style='display:inline'>Superglue server control panel</h2>
  272. <span style='display:inline; margin-left: 50px;'>System version: "$(cat /etc/superglue_version || echo 'n/a')" | Device: "$(cat /etc/superglue_model || echo 'n/a')" | OpenWRT: "$(cat /etc/openwrt_version || echo 'n/a')"</span>
  273. <span style='display:block;'>$(uptime)</span>
  274. <hr>
  275. Change password:
  276. <form method='post' action='/admin/pwdchange'>
  277. <input type='text' name='usr' value='admin' readonly>
  278. <input type='password' name='pwd'>
  279. <input type='password' name='pwdd'>
  280. <input type='submit' value='Apply'>
  281. </form>
  282. <hr>
  283. Configure wireless network:
  284. <form method='post' action='/admin/ssidchange'>
  285. <input type='text' name='ssid' value='$(getUci ssid)'>
  286. <input type='text' name='key' value='$(getUci key)'>
  287. <input type='hidden' name='pssid' value='$(getUci ssid)'>
  288. <input type='hidden' name='pkey' value='$(getUci key)'>
  289. <input type='submit' value='Apply'>
  290. </form>
  291. <hr>
  292. Update firmware:
  293. <form method='post' action='/admin/updatefw' enctype='multipart/form-data'>
  294. <input type='file' name='fwupload'>
  295. <input type='submit' id='upload' value='Upload'>
  296. </form>
  297. <hr>
  298. <form action='/admin/rebootnow' method='post' class='inline'>
  299. <input type='hidden' name='reboot' value='now' class='inline'>
  300. <input type='submit' value='Reboot' class='inline'>
  301. </form>
  302. <form action='http://logout@${HTTP_HOST}/admin' method='get' class='inline'>
  303. <input type='submit' value='Logout' class='inline'>
  304. </form>
  305. <hr>
  306. Memory:
  307. <pre>$(free)</pre>
  308. <hr>
  309. Storage:
  310. <pre>$(df -h)</pre>
  311. <hr>
  312. Environment:
  313. <pre>$(env)</pre>
  314. <hr>
  315. $_POST
  316. </body></html>"
  317. exit 0