Sunspot Home

more notes for a bad memory - - -

Performing root actions on a Raspberry Pi by CGI from a remote web page

I have used many routers with OpenWrt for years and easily controlled the router GPIO ports etc as if working as root.
It took some time to realise that the raspberry Pi is a very different animal and, as delivered, seems to hate control from a remote web page.

I want to include a button on the web page that will reboot or power off the Pi.
This is for local use on my LAN when I am running Kiosk mode without mouse or keyboard but with a small touch LED screen.

People say it is very dangerous to give a web page root access! - but my Pi is for local use -
(I hate the "nanny software" features in default Raspbian - a pi is just a jumped up micro not a bank mainframe!!).

After hours of Googling with nothing working I have found my own "workaround".

But first note some observations.
The owner of /usr/lib/cgi-bin and all of var/www/ etc. all show "root root" by ls -l
(like - -rwxr-xr-x 1 root root 3975 Nov 4 20:37 solar.bas)
- but even so the cgi-bin files can flash an LED on the Raspi GPIO (using apt-get install wiringPi)
e.g. this works in a bash script controlled from a cgi web page -

# 1 flash the LED
gpio mode 5 out
gpio write 5 0
sleep .2
gpio write 5 1
fi

And I can also call C code i2c drivers from Blassic - like
SHELL "/usr/lib/cgi-bin/solar/get_i2c_data.sh"
get_i2c_data.sh
contains lines like -
echo -n `/usr/sbin/i2c_lm75 76`>/var/www/ramdisk/T76.txt

- but the full path MUST be included even though i2c_lm75 76 will run alone in a terminal session

BUT

I am unable to run /sbin/reboot in any of my scripts - I tried many options in visudo to give www-data root access
(even though most linux developers think that will end the world as we know it)
- but none made it possible to run reboot from a web page - so I took them out to save the world and found -

MY SAFE FIX! - - - - - - - - - BUT SEE BELOW - I eventually managed to get reboot to run from a www-data script

echo -n "text">/var/www/ramdisk/reboot.txt does create a text file reboot.txt containing text (run by the web page)

I also run this "watchdog" in the background all the time - it starts at boot time as root-

#!/bin/bash
#/usr/lib/cgi-bin/solar/check_web_input.sh
# check to see if a button on web page has been pressed that demands root action
while :
do
# was the reboot button pressed on the web page?
# ie does reboot.txt file exist?
if [ -e "/var/www/ramdisk/reboot.txt" ]
then
echo "reboot.txt exists - now rebooting"
# now remove it
rm /var/www/ramdisk/reboot.txt
echo "reboot.txt removed"
reboot
else
echo "reboot.txt does not exist"
fi
sleep 1 # cycle every second
done

This performs a reboot as a root action and was triggered by pressing a link on the remote web page.
The web page does not run root scripts but does leave a "marker" that the root level "watchdog" (check_web_input.sh) acts on.

The files

/etc/init.d/graham_startup.sh

#! /bin/sh
#
# My startup script at
# /etc/init.d/graham_startup.sh
#
# note, when changed do the following at a ssh command line-
# update-rc.d -f graham_startup.sh remove
# update-rc.d -f graham_startup.sh defaults 99
# ignore LSB warnings

# Now carry out specific functions when asked to by the system
case "$1" in
start)

# make a ramdisk to save the flash
mkdir /var/www/ramdisk
mount -o size=4M -t tmpfs tmpfs /var/www/ramdisk

/usr/lib/cgi-bin/solar/check_web_input.sh &

;;
stop)
;;
*)
echo "Usage: /etc/init.d/graham_startup.sh {start|stop}"
exit 1
;;
esac

exit 0


/usr/lib/cgi-bin/solar/check_web_input.sh

#!/bin/bash
#/usr/lib/cgi-bin/solar/check_web_input.sh
# grab data from C programs and check to see if
# button on web page pressed that demands root action

while :
do

# was the reboot button pressed on the web page?
if [ -e "/var/www/ramdisk/reboot.txt" ]
then
echo "reboot.txt exists - now rebooting"
rm /var/www/ramdisk/reboot.txt
echo "reboot.txt removed"
reboot

else
echo "reboot.txt does not exist"
fi

sleep 1 # cycle every second
done


/usr/lib/cgi-bin/solar/solar.cgi

#!/bin/bash

#Decode query string

IFS="&"
for QUERY_PARTS in $QUERY_STRING; do
QKEY="`echo $QUERY_PARTS | cut -d '=' -f 1`"
QVAL="`echo $QUERY_PARTS | cut -d '=' -f 2`"
eval "`httpd -d "$QKEY"`=\"`httpd -d "$QVAL"`\""
# remove next line for OpenWrt
eval "$QKEY=\"$QVAL\""
done

# when the page is first opened load the text from pg-default.sh
# the pg-solar.sh page gets strings from the web page like ->
# index.cgi?page=solarsend&string=HARRY
# or even index.cgi?page=solarsend&string1=HARRY&string2=MARY
# (and as many strings as you want)
# so the values of both page and string are sent to this script
# thus if page=solar the script we run will be pg-solar.sh
# and it will have the strings from the web page available to use

# has the variable page been given a value?
if [ -e "pg-$page.sh" ]; then
inc="./pg-$page.sh"

# if not then show the default text
else
inc="./pg-default.sh"
page="default"
fi

echo "Content-type: text/html"
echo
cat solar.html

echo "<!-- Content goes here -->"
. $inc
echo "<!-- End of content -->"

#you can add shell command line commands here such as-
echo "Raspi uptime: `cat /proc/uptime | cut -d ' ' -f 1` seconds.<br>"

# now finish the html
echo "<BR></body></html>"

# or finish with a bottom half page solar_bottom.html
# cat solar_bottom.html - put it into /cgi-bin/solar/


/usr/lib/cgi-bin/solar/solar.html

<html>
<head>
<title>solar</title>
<META HTTP-EQUIV='refresh' CONTENT='5; URL=http://192.168.0.84/cgi-bin/solar/solar.cgi?page=solar&string1=a'>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">

</head>
<body bgcolor="#FFFFCC">

<a href="solar.cgi?page=solar&string1=a">1 flash </a> . . .
<a href="solar.cgi?page=solar&string1=b">2 flashes </a> . . .
<a href="solar.cgi?page=solar&string1=reboot">reboot</a><BR>
<HR>


/usr/lib/cgi-bin/solar/pg-solar.sh

#!/bin/bash
#/usr/lib/cgi-bin/solar/pg-solar.sh

#get the system date and time and present them in a new format
datestring=`date`
dateonly=`expr substr $datestring 1 10`
# timeonly=`expr substr $datestring 12 8` with seconds display
timeonly=`expr substr $datestring 12 5`
yearonly=`expr substr $datestring 25 4`
timedate=$timeonly" "$dateonly

# for testing
#echo "<BR>pg-solar.sh now printing - <BR>"
#echo $timedate "<BR>"
#echo "string received from solar.cgi, string1 = " $string1 "<br>"

if [ $string1 = "a" ]; then
# 1 flash on the LED
echo -n "">/var/www/ramdisk/a.txt
gpio mode 5 out
gpio write 5 0
sleep .2
gpio write 5 1
fi

if [ $string1 = "b" ]; then
# 2 flashes on the LED
gpio mode 5 out
gpio write 5 0
sleep .2
gpio write 5 1
sleep .2
gpio write 5 0
sleep .2
gpio write 5 1
fi

#respond to web requests as root (check_web_input.sh must run at boot time)
if [ $string1 = "reboot" ]; then
echo -n "text">/var/www/ramdisk/reboot.txt
fi

#echo "<BR><HR>"
# now call the Blassic basic program - pass on 2 strings as arguments along with the timedate string
/usr/lib/cgi-bin/solar/solar.bas


/usr/lib/cgi-bin/solar/pg-default.sh

#!/bin/bash

#Generate the message that is first seen

echo "<BR><font color=red size=3 face=Arial>"
echo "This is the default page (pg-default.sh)<BR>"
echo "</font>"


/usr/lib/cgi-bin/solar/solar.bas

#!/usr/sbin/blassic
' /usr/lib/cgi-bin/solar/solar.bas
' solar.bas uses the Blassic interpreter
' and is sent variable $string1
' from the bash script pg-solar.sh that is called by solar.cgi
' that sets up the web page seen by the remote user.
' (The top part of the web page is from solar.html)

' Sunspot Nov 2015 - www.sunspot.co.uk
' inspired by code found in a Sweex router

string1$ = programarg$(1)
string2$ = programarg$(1)

'PRINT "string1$ = ", string1$, " . . . . . . string2$ = ", string2$
'PRINT "get 12c data next"
SHELL "/usr/lib/cgi-bin/solar/get_i2c_data.sh"
'PRINT "got it"

'======================================================================================================

'read the LM75 thermometers on the tanks
'T7X$ are saved by get_i2c_data.sh

' bring the temperature values into blassic as strings
OPEN "/var/www/ramdisk/T72.txt" FOR INPUT AS #1 : INPUT #1,T72$ : CLOSE #1
PAUSE 50
OPEN "/var/www/ramdisk/T74.txt" FOR INPUT AS #1 : INPUT #1,T74$ : CLOSE #1
PAUSE 50
OPEN "/var/www/ramdisk/T75.txt" FOR INPUT AS #1 : INPUT #1,T75$ : CLOSE #1
PAUSE 50
OPEN "/var/www/ramdisk/T75.txt" FOR INPUT AS #1 : INPUT #1,T75$ : CLOSE #1
PAUSE 50
OPEN "/var/www/ramdisk/T76.txt" FOR INPUT AS #1 : INPUT #1,T76$ : CLOSE #1
PAUSE 50
OPEN "/var/www/ramdisk/T77.txt" FOR INPUT AS #1 : INPUT #1,T77$ : CLOSE #1
PAUSE 50

OPEN "/var/www/ramdisk/port_6_status_words.txt" FOR INPUT AS #1 : INPUT #1,IM_HTR$ : CLOSE #1
OPEN "/var/www/ramdisk/port_5_status_words.txt" FOR INPUT AS #1 : INPUT #1,PUMP$ : CLOSE #1
OPEN "/var/www/ramdisk/solar_byte.txt" FOR INPUT AS #1 : INPUT #1,LIGHT_LEVEL$ : CLOSE #1

IF IM_HTR$ = "IM_HTR_ON" THEN IM_HTR$ = "<strong><font color=red size='3' face='Arial, Helvetica, sans-serif'>heater ON </font><strong>"
IF IM_HTR$ = "IM_HTR_OFF" THEN IM_HTR$ = "<strong><font color=black size='3' face='Arial, Helvetica, sans-serif'>heater OFF </font><strong>"

IF PUMP$ = "PUMP___ON" THEN PUMP$ = "<strong><font color=red size='3' face='Arial, Helvetica, sans-serif'>pump ON </font><strong>"
IF PUMP$ = "PUMP___OFF" THEN PUMP$ = "<strong><font color=black size='3' face='Arial, Helvetica, sans-serif'>pump OFF </font><strong>"
LIGHT_LEVEL_PRINTOUT$ = "<strong><font color=black size='3' face='Arial, Helvetica, sans-serif'>light = "+LIGHT_LEVEL$+"</font><strong>"

body1$ = "<strong><font color=red size='5' face='Arial, Helvetica, sans-serif' >Solar Hot Water</font></font></strong><br> <font color=red size='4' face='Arial, Helvetica, sans-serif'><strong>"+time$+"</font></strong>"
body_inlet$ = "<font color=blue size='3' face='Arial, Helvetica, sans-serif'><strong> . . . . inlet "+T72$+"</font></strong>"
body2$ = "<table width='214' border='5' bgcolor=white ><tr><td width='89' bgcolor=white align='center'><font color=green size='3' face='Arial, Helvetica, sans-serif'><strong>Main tank </font></strong></td><td width='109' align='center'><font color=green size='3' face='Arial, Helvetica, sans-serif'><strong>Feeder tank </font></strong></td></tr>"
body3$ = "<tr><td align='center'><strong><font color=blue size='3' face='Arial, Helvetica, sans-serif'>"+T77$+"</font></td><td align='center'><font color=blue size='3' face='Arial, Helvetica, sans-serif'><strong>"+T76$+"</font></strong></td></tr>"
body4$ = "<tr><td align='center'><strong><font color=blue size='3' face='Arial, Helvetica, sans-serif'>"+T75$+"</font></td><td align='center'><font color=blue size='3' face='Arial, Helvetica, sans-serif'><strong>"+T74$+"</font></strong></td></tr></table>"
body5$ = IM_HTR$
body6$ = PUMP$
body7$ = LIGHT_LEVEL_PRINTOUT$
bodybottom$ = "</body></html>"

PRINT head$, body1$, body_inlet$, body2$, body3$, body4$, body5$, body6$, body7$, bodybottom$, "<BR>"

'======================================================================================================

SYSTEM

'------------------------------------------------------------------------------------


/usr/lib/cgi-bin/solar/get_i2c_data.sh
#!/bin/bash
#/usr/lib/cgi-bin/solar/get_i2c_data.sh
echo -n `/usr/sbin/i2c_lm75 72`>/var/www/ramdisk/T72.txt
echo -n `/usr/sbin/i2c_lm75 74`>/var/www/ramdisk/T74.txt
echo -n `/usr/sbin/i2c_lm75 75`>/var/www/ramdisk/T75.txt
echo -n `/usr/sbin/i2c_lm75 76`>/var/www/ramdisk/T76.txt
echo -n `/usr/sbin/i2c_lm75 77`>/var/www/ramdisk/T77.txt
echo -n "234">/var/www/ramdisk/graham.txt
/usr/lib/cgi-bin/solar/solar-set_tank-read_pump_heater_lightlevel2.bas OFF

The web page
solar web page

Make the Pi show this screen in full screen mode at boot time

/etc/xdg/lxsession/LXDE-pi/autostart

@lxpanel --profile LXDE-pi
@pcmanfm --desktop --profile LXDE-pi

# kill the screen saver
#@xscreensaver -no-splash

@/home/pi/fullscreen.sh

/home/pi/fullscreen.sh

#! /bin/sh
# /home/pi/fullscreen.sh

# launch the web page of choice
sudo -u pi epiphany-browser -a --profile ~/.config http://127.0.0.1/cgi-bin/solar/solar.cgi?page=solar&string1=a --display=:0 > /dev/null 2>&1 &
sleep 15s;

# this does the same job as pressing the F11 key to force Kiosk mode
xte "key F11" -x:0 &

solar&string1=a makes the web page force the LED on 5 to flash

for xte do
apt-get install xautomation

 

I can now get reboot etc to work from a www-data controlled script (at last !!).

The top section of the web page http://192.168.0.xx/cgi-bin/solar/solar.cgi?page=solar&string1=solar (above)
that is built from file "/usr/lib/cgi-bin/solar/solar.html" contains the link
<a href="solar.cgi?page=solar&string1=rebootthepi">reboot</a> . <BR>

The script /usr/lib/cgi-bin/solar/pg-solar.sh responds to string1 in that link -

if [ $string1 = "rebootthepi" ]; then
echo "trying to reboot now<BR>"
sudo reboot
fi

And, at last, using a web page link I can perform root actions !!

sudo reboot now works because /etc/group contains
sudo:x:27:pi,www-data
So www-data and pi are both members of group sudo

to achieve that I did -
usermod -a -G sudo www-data
(help was eventually found here and some clues here)

I used visudo to edit /etc/sudoers/ and added -
www-data ALL=NOPASSWD:/sbin/reboot
www-data ALL=NOPASSWD:/sbin/poweroff

so only reboot and poweroff can be requested by the browser user and the root password is not required.

I note that GPIO and i2c seem to be controlled from a browser by www-data without all this fuss - why? - email please!
I see that my /etc/group contains i2c:x:111:pi,www-data
- so pi and www-data are members of 12c group - who set that up?
- but why should the i2c group work from a cgi script as root?

 


Please email me if you want to swap notes

SUNSPOT HOME

more to come . . .