overkiller
Posts: 4
Joined: Wed Dec 17, 2014 7:23 am

C++ program executed from PHP not exporting GPIO properly

Sun Oct 09, 2016 8:26 am

Hello there,
I'm writing simple PHP webapp to control devices around the house. I wrote small C/C++ console application to easily export, read, write GPIOs. It's using sysfs in order to control them.
The PHP webapp is running on Apache2, which user is added to gpio group, so it should be able to control GPIOs.
The problem is that if I execute the app from bash console it works without any problem, but if I make PHP to execute it, then the GPIO is exported but when I try to write value to the GPIO it just does not work.

Basic info:

Code: Select all

Board: Raspberry Pi B+
Kernel: Linux raspberrypi 4.4.13+ #894 Mon Jun 13 12:43:26 BST 2016 armv6l GNU/Linux
Disto: Raspian
Apache2 user: www-data
www-data groups: 
uid=33(www-data) gid=33(www-data) grupy=33(www-data),44(video),997(gpio)
C/C++ code:

Code: Select all

#include <cstdio>
#include <iostream>
#include "GPIO.h"
#include "Utility.h"
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string>
#define IN  0
#define OUT 1

#define LOW  0
#define HIGH 1
static int
GPIOExport(int pin)
{
#define BUFFER_MAX 3
	char buffer[BUFFER_MAX];
	ssize_t bytes_written;
	int fd;

	fd = open("/sys/class/gpio/export", O_WRONLY);
	if (-1 == fd) {
		fprintf(stderr, "Failed to open export for writing!\n");
		return(-1);
	}

	bytes_written = snprintf(buffer, BUFFER_MAX, "%d", pin);
	write(fd, buffer, bytes_written);
	close(fd);
	return(0);
}

static int
GPIOUnexport(int pin)
{
	char buffer[BUFFER_MAX];
	ssize_t bytes_written;
	int fd;

	fd = open("/sys/class/gpio/unexport", O_WRONLY);
	if (-1 == fd) {
		fprintf(stderr, "Failed to open unexport for writing!\n");
		return(-1);
	}

	bytes_written = snprintf(buffer, BUFFER_MAX, "%d", pin);
	write(fd, buffer, bytes_written);
	close(fd);
	return(0);
}

static int
GPIODirection(int pin, int dir)
{
	static const char s_directions_str[] = "in\0out";

#define DIRECTION_MAX 35
	char path[DIRECTION_MAX];
	int fd;

	snprintf(path, DIRECTION_MAX, "/sys/class/gpio/gpio%d/direction", pin);
	fd = open(path, O_WRONLY);
	if (-1 == fd) {
		fprintf(stderr, "Failed to open gpio direction for writing!\n");
		return(-1);
	}

	if (-1 == write(fd, &s_directions_str[IN == dir ? 0 : 3], IN == dir ? 2 : 3)) {
		fprintf(stderr, "Failed to set direction!\n");
		return(-1);
	}

	close(fd);
	return(0);
}

static int
GPIORead(int pin)
{
#define VALUE_MAX 30
	char path[VALUE_MAX];
	char value_str[3];
	int fd;

	snprintf(path, VALUE_MAX, "/sys/class/gpio/gpio%d/value", pin);
	fd = open(path, O_RDONLY);
	if (-1 == fd) {
		fprintf(stderr, "Failed to open gpio value for reading!\n");
		return(-1);
	}

	if (-1 == read(fd, value_str, 3)) {
		fprintf(stderr, "Failed to read value!\n");
		return(-1);
	}

	close(fd);

	return(atoi(value_str));
}

static int
GPIOWrite(int pin, int value)
{
	static const char s_values_str[] = "01";

	char path[VALUE_MAX];
	int fd;

	snprintf(path, VALUE_MAX, "/sys/class/gpio/gpio%d/value", pin);
	fd = open(path, O_WRONLY);
	if (-1 == fd) {
		fprintf(stderr, "Failed to open gpio value for writing!\n");
		return(-1);
	}

	if (1 != write(fd, &s_values_str[LOW == value ? 0 : 1], 1)) {
		fprintf(stderr, "Failed to write value!\n");
		return(-1);
	}

	close(fd);
	return(0);
}
int main(int argc, char *argv[])
{
	///if argc = 3 -> init pin
	///second is pin number;
	///we expect that third parameter is direction
	if (argc == 3)
	{
		std::string secondParameterStr(argv[1]);
		std::string thirdParameterStr(argv[2]);
		int pin = 0;
		pin = atoi(secondParameterStr.c_str());
		if (thirdParameterStr == "out")
		{
			GPIODirection(pin, OUT);
		}
		else if (thirdParameterStr == "in")
		{
			GPIODirection(pin, IN);
		}
		else if (thirdParameterStr == "export")
		{
			GPIOExport(pin);
		}
		else if (thirdParameterStr == "kill")
		{
			GPIOUnexport(pin);
		}
		else if (thirdParameterStr == "1")
		{
			GPIOWrite(pin, 1);
		}
		else if (thirdParameterStr == "0")
		{
			GPIOWrite(pin, 0);
		}
		else if (thirdParameterStr == "read")
		{
			return GPIORead(pin);
		}
	}

    return 2; // no value
}
Related PHP code:

Code: Select all

    /**
     * @Route("/addPin", name="addPin")
     */
    public function pinAction(Request $request)
    {

      $pin = new Pin();
      $form = $this->createFormBuilder($pin)
        ->add('PinNumber', IntegerType::class)
        ->add('PinHeaderLocation', TextType::class)
        ->add('State', IntegerType::class)
        ->add('Save', SubmitType::class, array('label' => 'Add Pin'))
        ->getForm();

      $form->handleRequest($request);
      if($form->isSubmitted() && $form->isValid())
      {
        try
        {
          $pin = $form->getData();
          if($pin->getPinNumber() < 1)
            return $this->redirectToRoute("addPinFailure");
          $number = $pin->getPinNumber();
          \AppBundle\Utils\FileInteractor::ExecuteFileAndGetContents("bin/GPIOController.out $number export");
          \AppBundle\Utils\FileInteractor::ExecuteFileAndGetContents("bin/GPIOController.out $number out");
          $orm = $this->getDoctrine()->getManager();
          $orm->persist($pin);
          $orm->flush();

          return $this->redirectToRoute("addPinSuccess");
        } catch (Exception $e)
        {
          return $this->redirectToRoute("addPinFailure");
        }
      }
      return $this->render("home/gpio.php.twig", array(
        'form' => $form->createView(),
        'title' => "RaspiStatus",
      ));
    }
PHP function to executing files:

Code: Select all

  public static function ExecuteFileAndGetContents($path="")
  {
    $output = "";
    if($path != null)
    {
      $output = exec($path);
      return $output;
    }
    return $output;
  }

swampdog
Posts: 420
Joined: Fri Dec 04, 2015 11:22 am

Re: C++ program executed from PHP not exporting GPIO properl

Wed Oct 12, 2016 10:22 pm

I don't know php but replace your code with a bit of shell script which executes your code. That will allow you to edit the shell script to see what is going on.

#!/bin/bash
touch /tmp/z
echo $PATH
[etc]

mutley
Posts: 61
Joined: Sat Jan 02, 2016 8:06 pm

Re: C++ program executed from PHP not exporting GPIO properl

Thu Oct 13, 2016 6:56 pm

Check the permissions. Add apache user to /etc/sudoers with your executable and nopassword option, then execute it with "sudo executable" within PHP.
Something like
www-data 192.168.1.0/255.255.255.0=(root) NOPASSWD: /usr/local/bin/mycommand

(Obviously change the IP and mycommand to your system)

I've had similar problems, and it's usually either the environment variables of the shell, or a privileges issue.

User avatar
DougieLawson
Posts: 40184
Joined: Sun Jun 16, 2013 11:19 pm
Location: A small cave in deepest darkest Basingstoke, UK
Contact: Website Twitter

Re: C++ program executed from PHP not exporting GPIO properl

Sat Oct 15, 2016 8:19 am

mutley wrote:Check the permissions. Add apache user to /etc/sudoers with your executable and nopassword option, then execute it with "sudo executable" within PHP.
DO NOT DO THAT. It opens a massive security hole.

Use wiringPi in non-privileged mode. Use pigpio / pigpiod which allows a non-privileged client to drive the gpio using the privileged pigpiod server.

Do anything other than allowing Apache2 to run with sudo.
Criticising any questions is banned on this forum.

Any DMs sent on Twitter will be answered next month.
All fake doctors are on my foes list.

Note: Any requirement to use a crystal ball or mind reading will result in me ignoring your question.

mutley
Posts: 61
Joined: Sat Jan 02, 2016 8:06 pm

Re: C++ program executed from PHP not exporting GPIO properl

Mon Oct 17, 2016 1:36 pm

DougieLawson wrote:
mutley wrote:Check the permissions. Add apache user to /etc/sudoers with your executable and nopassword option, then execute it with "sudo executable" within PHP.
DO NOT DO THAT. It opens a massive security hole.

Use wiringPi in non-privileged mode. Use pigpio / pigpiod which allows a non-privileged client to drive the gpio using the privileged pigpiod server.

Do anything other than allowing Apache2 to run with sudo.
Sorry for the confusion, when I said "Check the permissions" I meant just that, ie only while debugging / finding the root of the problem. Not to leave the system like that, just see if it is actually is a permissions while executing problem.

But I will say that is not a massive security hole, you are using the sudoers file as designed, to elevate privileges to just one binary and one user. To exploit that fully, someone must have access to your system and be able to replace that binary with a script / binary of their own. If they are that far into your system, this is the least of your concerns.

Return to “C/C++”