Sudo, exec und das sudowebscript.

Heute ist Stammtischzeit:
Jeden Donnerstag 20:30 Uhr hier im Chat.
Wer Lust hat, kann sich gerne beteiligen. ;)
  • Hallo,

    "Meine bisherigen Erfahrungen mit dem Raspberry und allem drumherum"

    ich habe mir erst vor etwa zwei Wochen einen Raspberry gegönnt und hatte vorher nur sporadisch Erfahrung mit Linux. Meine Erfahrungen mit HTML liegen schon viele Jahre zurück und umfassten auch nicht wirklich viel, von PHP hatte ich bis letzten Freitag absolut gar keine Ahnung. Ich bin also wahrlich ein Neuling in der Materie.

    Ich habe mich bisher größtenteils an Anleitungen gehalten. Zunächst hatte ich mal den Raspberry-Pi-Emulator realisiert, mich dann aber schnell etwas anderem zugewandt.

    "Derzeitiges Projekt"

    Mein derzeitiges Projekt sieht vor, dass ich ein paar Funksteckdosen und den Musikplayer mit einer Weboberfläche steuern kann. Die Steuerung des Musikplayers klappt soweit auch, da hier die Befehle nicht mit root-Rechten ausgeführt werden müssen. Die Steuerung der Steckdosen klappt bisher aber nur über die Eingabe in der Kommandozeile (sudo raspberry-remote/send 11100 1 1. Gearbeitet habe ich bisher nach diesem Tutorial. Letztlich klappt die Bedienung nicht und ich vermute, dass es irgendwelche Probleme mit den Rechten gibt. Die präsentierte Lösung den User www-data in die sudoers-Liste mit allen Rechten einzutragen erscheint mir mehr als sub-optimal zu sein, auch für den Fall, dass der Pi nicht mit dem Internet verbunden ist.
    Ich habe jetzt schon vieles versucht, nichts hat geklappt, außer einer Lösung mit einem Skript, dass mit raspberry-remote mitgeliefert wurde. Diese gefällt mir aber noch nicht so ganz, was einerseits daran liegt, dass man immer den daemon ausführen muss (oder ein Skript schreibt, dass ihn ausführt) und sobald ich einen Schalter drücke, keine weiteren Eingaben in die Kommandozeile möglich sind. Weiter kann ich diese Lösung aber auch nicht wirklich nachvollziehen.

    Um die Funksteckdosen über das Webinterface zu steuern habe ich mich letztendlich für die Variante mit dem sudowebscript entschieden. Diese erscheint mir eine sichere und flexible Variante zu sein. Leider komme ich damit noch nicht so ganz klar, wie es mir scheint. Im folgenden poste ich einfach mal meine Skripte, php-dateien etc.


    Mein sudowebscript sieht zur Zeit aus wie folgt. Wie ihr erkennen könnt, habe ich mir hier im Forum umgeschaut ;)

    "sudowebscript.sh"


    [code=php]
    #!/bin/bash
    #
    # sudo web script allowing user www-data to run commands with root privilegs
    #
    # required /etc/sudoers Entry:
    # www-data ALL=NOPASSWD:/var/sudowebscript.sh
    #

    case "$1" in
    remote) /home/pi/raspberry-remote/send $2 $3 $4 ;;
    *) echo "ERROR: invalid parameter: $1 (for $0)"; exit 1 ;;
    esac

    exit 0
    [/php]

    Die Rechte sehen aus wie folgt:

    "Rechte sudowebscript"

    [/img]

    Das Webinterface zum Steuern der Steckdosen und des Musikplayers sieht zur Zeit aus wie folgt. Dabei habe ich mich zum einen aus dem angesprochenen Tutorial sowie aus dem Forum hier bedient. Auch ein paar eigene Ideen sind eingeflossen (die Steuerung des Musikplayers...ohh welch Geniestreich...). Durch das Bedienen hier im Forum hab ich auch Teile für's debugging im Code, was euch vielleicht hilft. Wahrscheinlich ist der/sind die Fehler aber für euch eh offensichtlich.

    "Heimautomation.php"


    [code=php]
    <!DOCTYPE html>
    <html>


    <head>
    <meta name="viewport" content="width=device-width"/>
    <title>Heimautomation via Raspberry</title>
    </head>


    <body>

    Lampen schalten
    <form method="get" action="Heimautomation.php">
    <input type="submit" value="Lampen an" name="Lampenein">
    <input type="submit" value="Lampen aus" name="Lampenaus">
    </form>
    <br/>
    <br/>
    Musik
    <form method="get" action="Heimautomation.php">
    <input type="submit" value="Musik an/aus" name="togglemusic">
    <input type="submit" value="Musik lauter" name="volumeup">
    <input type="submit" value="Musik leiser" name="volumedown">
    <input type="submit" value="Nächstes Lied" name="nexttrack">
    </form>


    <?php
    #$PREcmd = 'sudo ';
    #$SudoWebScript = '/var/sudowebscript.sh ';

    if(isset($_GET["Lampenein"])){
    exec('sudo /var/sudowebscript.sh remote 11100 1 1', $output,
    $return_var);
    }
    if(isset($_GET["Lampenaus"])){
    exec('sudo /var/sudowebscript.sh remote 11100 1 0', $output,
    $return_var);
    }

    if(isset($_GET["togglemusic"])){
    exec("mpc toggle");
    echo "Musik an/aus";
    }
    if(isset($_GET["volumeup"])){
    exec("mpc volume +5");
    echo "Volume +5";
    }
    if(isset($_GET["volumedown"])){
    exec("mpc volume -5");
    echo "Volume -5";
    }
    if(isset($_GET["nexttrack"])){
    exec("mpc next");
    echo "Nächstes Lied";
    }
    # Error/Output Handling
    if (isset($return_var) AND $return_var >= 1) {
    echo "<font face='Arial, Helvetica, sans-serif' size='3' color='FF0000'>ERROR: </font>\n";
    echo "<font face='Arial, Helvetica, sans-serif' size='3' color='009900'>".exitcode($return_var)."<br/>\n";
    }
    if (isset($output) AND !empty($output)) {
    foreach($output AS $line) { echo $line."<br/>\n"; }
    }

    // Function to handle $return_var (exit codes)
    /*
    An exit status of zero indicates success. A non-zero exit status indicates failure.
    When a command terminates on a fatal signal N, bash uses the value of 128+N as the exit status.

    If a command is not found, the child process created to execute it returns a status of 127. If a com-
    mand is found but is not executable, the return status is 126.

    If a command fails because of an error during expansion or redirection, the exit status is greater than
    zero.

    Shell builtin commands return a status of 0 (true) if successful, and non-zero (false) if an error
    occurs while they execute. All builtins return an exit status of 2 to indicate incorrect usage.

    Bash itself returns the exit status of the last command executed, unless a syntax error occurs, in
    which case it exits with a non-zero value.

    source: http://tldp.org/LDP/abs/html/exitcodes.html
    */
    function exitcode($code) {
    $ReturnCode['0'] = "Successful";
    $ReturnCode['1'] = "General Error (Miscellaneous errors, such as 'divide by zero' and other impermissible operations)";
    $ReturnCode['2'] = "Incorrect Usage";
    $ReturnCode['126'] = "Command found but not executable (Permission problem)";
    $ReturnCode['127'] = "Command not found (Possible problem with PATH or a typo)";
    $ReturnCode['128'] = "Invalid argument to exit (exit takes only integer args in the range 0 - 255)";
    $ReturnCode['130'] = "Script terminated by Control-C";
    return $ReturnCode[$code];
    }
    ?>
    </body>
    </html>
    [/php]

    Auch hier noch die Rechte:

    &quot;Rechte Heimautomation.php&quot;


    [/img]

    Zuletzt noch die sudoers-datei:

    &quot;sudoers&quot;


    [/img]

    Ich hoffe, dass ihr mir helfen könnt und bedanke mich schon mal im Voraus für die Mühe :)


    Edit: Ich hab mal die Verbesserungen von meigrafd eingebaut, sowie für etwas mehr Lesbarkeit gesorgt. Weiter ist mir beim ganzen Verändern des Codes mittlerweile ein Fehler unterlaufen gewesen, den ich auch verbessert habe. Die Verbesserungen habe ich in grün kenntlich gemacht.

  • Im sudowebscript.sh übergibst du keine Parameter an den send Befehl. Was soll der denn dann machen :huh:

    Versuch es also mal damit:[code=php]#!/bin/bash
    #
    # sudo web script allowing user www-data to run commands with root privilegs
    #
    # required /etc/sudoers Entry:
    # www-data ALL=NOPASSWD:/var/sudowebscript.sh
    #

    case "$1" in
    remote) /home/pi/raspberry-remote/send $2 $3 ;;
    *) echo "ERROR: invalid parameter: $1 (for $0)"; exit 1 ;;
    esac

    exit 0[/php]So wird das zweite und dritte Parameter (oder Argument), welches an das Script übergeben wird, auch an den Befehl weitergegeben.
    Das 1. Parameter wäre "remote" und das 2. zB "11100" und das 3. wäre "1"


    Dann sieht aber auch noch dein Heimautomation.php komisch aus:
    [code=php]if(isset($_GET["Lampenein"])){
    exec('sudo /var/sudowebscript.sh remote '.'11100 1', $output, $return_var);
    }[/php]Was macht das '.' da :huh:


    Und zu guter letzt: Gewöhnt euch bitte an Einrückungen vorzunehmen, das lässt sich besser lesen.

    Unschön:[code=php]if(isset($_GET["Lampenein"])){
    exec('sudo /var/sudowebscript.sh remote 11100 1', $output, $return_var);
    }[/php]

    Besser:
    [code=php]if (isset($_GET["Lampenein"])) {
    exec('sudo /var/sudowebscript.sh remote 11100 1', $output, $return_var);
    }[/php]

  • Danke schon mal für deine Antwort. Wie du siehst hab ich das sudowebscript noch nicht ganz verstanden ;) Dein Beitrag war aber schon sehr hilfreich. Dummerweise habe ich im ersten Beitrag mittlerweile einen Fehler eingebaut. Der Send Befehl benötigt drei weitere Argumente und lautet beispielsweise .../send 11100 1 1. Dementsprechend habe ich die Quellcodes jetzt angepasst.

    &quot;sudowebscript.sh&quot;


    [code=php]
    #!/bin/bash
    #
    # sudo web script allowing user www-data to run commands with root privilegs
    #
    # required /etc/sudoers Entry:
    # www-data ALL=NOPASSWD:/var/sudowebscript.sh
    #

    case "$1" in
    remote) /home/pi/raspberry-remote/send $2 $3 $4 ;;
    *) echo "ERROR: invalid parameter: $1 (for $0)"; exit 1 ;;
    esac

    exit 0
    [/php]

    &quot;Heimautomation.php&quot;


    [code=php]
    <!DOCTYPE html>
    <html>


    <head>
    <meta name="viewport" content="width=device-width"/>
    <title>Heimautomation via Raspberry</title>
    </head>


    <body>

    Lampen schalten
    <form method="get" action="Heimautomation.php">
    <input type="submit" value="Lampen an" name="Lampenein">
    <input type="submit" value="Lampen aus" name="Lampenaus">
    </form>
    <br/>
    <br/>
    Musik
    <form method="get" action="Heimautomation.php">
    <input type="submit" value="Musik an/aus" name="togglemusic">
    <input type="submit" value="Musik lauter" name="volumeup">
    <input type="submit" value="Musik leiser" name="volumedown">
    <input type="submit" value="Nächstes Lied" name="nexttrack">
    </form>


    <?php
    #$PREcmd = 'sudo ';
    #$SudoWebScript = '/var/sudowebscript.sh ';

    if (isset($_GET["Lampenein"])) {
    exec('sudo /var/sudowebscript.sh remote 11100 1 1', $output, $return_var);
    }
    if (isset($_GET["Lampenaus"])) {
    exec('sudo /var/sudowebscript.sh remote 11100 1 0', $output, $return_var);
    }

    if(isset($_GET["togglemusic"])){
    exec("mpc toggle");
    echo "Musik an/aus";
    }
    if(isset($_GET["volumeup"])){
    exec("mpc volume +5");
    echo "Volume +5";
    }
    if(isset($_GET["volumedown"])){
    exec("mpc volume -5");
    echo "Volume -5";
    }
    if(isset($_GET["nexttrack"])){
    exec("mpc next");
    echo "Nächstes Lied";
    }
    # Error/Output Handling
    if (isset($return_var) AND $return_var >= 1) {
    echo "<font face='Arial, Helvetica, sans-serif' size='3' color='FF0000'>ERROR: </font>\n";
    echo "<font face='Arial, Helvetica, sans-serif' size='3' color='009900'>".exitcode($return_var)."<br/>\n";
    }
    if (isset($output) AND !empty($output)) {
    foreach($output AS $line) { echo $line."<br/>\n"; }
    }

    // Function to handle $return_var (exit codes)
    /*
    An exit status of zero indicates success. A non-zero exit status indicates failure.
    When a command terminates on a fatal signal N, bash uses the value of 128+N as the exit status.

    If a command is not found, the child process created to execute it returns a status of 127. If a com-
    mand is found but is not executable, the return status is 126.

    If a command fails because of an error during expansion or redirection, the exit status is greater than
    zero.

    Shell builtin commands return a status of 0 (true) if successful, and non-zero (false) if an error
    occurs while they execute. All builtins return an exit status of 2 to indicate incorrect usage.

    Bash itself returns the exit status of the last command executed, unless a syntax error occurs, in
    which case it exits with a non-zero value.

    source: http://tldp.org/LDP/abs/html/exitcodes.html
    */
    function exitcode($code) {
    $ReturnCode['0'] = "Successful";
    $ReturnCode['1'] = "General Error (Miscellaneous errors, such as 'divide by zero' and other impermissible operations)";
    $ReturnCode['2'] = "Incorrect Usage";
    $ReturnCode['126'] = "Command found but not executable (Permission problem)";
    $ReturnCode['127'] = "Command not found (Possible problem with PATH or a typo)";
    $ReturnCode['128'] = "Invalid argument to exit (exit takes only integer args in the range 0 - 255)";
    $ReturnCode['130'] = "Script terminated by Control-C";
    return $ReturnCode[$code];
    }
    ?>
    </body>
    </html>
    [/php]

    Als Fehlercode kommt weiterhin: ERROR: General Error (Miscellaneous errors, such as 'divide by zero' and other impermissible operations).

    Wem sollte denn die Datei sudowebscript.sh gehören? root oder www-data, oder spielt das keine Rolle? Muss diese Datei noch in den Ordner /etc/sudoers.d reinkopiert werden oder reicht der Eintrag in die Sudoers-Liste?

    Anbei noch ein Screenshot des send-Befehls in der Kommandozeile. Vielleicht hilft es ja.
    sendbefehltjs2kfrwig.jpg

    Edit: Sobald ich nach Aufrufen des sudowebscripts einen Befehl eingebe, der dort nicht vermerkt ist, sollte doch eine Fehlermeldung kommen, sofern ich das richtig verstehe (dafür steht doch der Stern beim case, quasi ein else, oder?). Die kommt allerdings nicht. Heißt das nicht, dass das Skript überhaupt nicht ausgeführt wird?

  • Wem die Datei /var/sudowebscript.sh gehört ist unwichtig, da es über sudo also root ausgeführt wird und root darf alles ;)
    Was aber sein muss ist dass diese Datei Ausführrechte besitzt, damit der Shebang zur Geltung kommt.

  • Ok, dann ist das mit dem Besitzt zumindest mal geklärt :)
    Aber soweit ich das Beurteilen kann, hab ich die Datei doch ausführbar gemacht. Schau mal in meinen Eröffnungsbeitrag, da hab ich einen Screenshot von den Rechten. Das "x" steht doch dort für execute, oder nicht?

  • Nein das ist kein genereller Weg um Fehler zu testen... Aber es vorher auf der Konsole auszuführen um zu prüfen obs geht ist im Sinne von Ausschlussverfahren schon nicht verkehrt :D

    Aber nun sieht man Deinen Fehler: Du scheinst das Script unter Windows erstellt zu haben. Linux versteht aber nicht die Zeilenumbruchszeichen von Windows. Deshalb auch zB der Fehler: /bin/bash^M

    Lösch das Script: rm -f /var/sudowebscript.sh
    Erstell es direkt unter Linux neu: nano /var/sudowebscript.sh
    und copy&paste den Inhalt rein
    Dann speichern+beenden und ausführbar machen.

    Für das nächste mal: Wenn du es unbedingt unter Windoof bearbeiten willst dann verwende einen Linux-kompatiblen Editor wie zB notepad++ oder UltraEdit.

  • Ahoi,

    du hast das Skript unter Windows erstellt und dann auf den Pi kopiert. Das führt zu Problemen, da Windows und Linux verschiedene Zeilenenden verwenden.

    Am besten löschst du das Skript:

    Code
    sudo rm /var/sudowebscript.sh


    Erstellst eine Datei:

    Code
    sudo touch /var/sudowebscript.sh


    Machst diese ausführbar:

    Code
    sudo chmod +x /var/sudowebscript.sh


    Öffnest die Datei in einem Editor:

    Code
    sudo nano /var/sudowebscript.sh


    Kopierst dort dann den Quellcode hinein, und schließt den Editor mit Strg+X, Y, Enter.
    Dann versuchst du den Aufruf erneut:

    Code
    /var/sudowebscript.sh remote 11100 1 1
  • Super vielen Dank :danke_ATDE: Das hat geholfen :thumbs1: Darauf wäre ich von selbst wohl nie gekommen, da hätte ich noch so viel im Netz lesen können :D
    Bisher habe ich mit komodo edit gearbeitet. Dieser Editior kann das wohl nicht^^ Werde dann wohl wechseln :)

    Eine Frage hätte ich grade noch, die nur am Rande was damit zu tun hat:
    Ich schreibe die Quellcodes zur Zeit an meinem Hauptrechner mit Windows 7 und möchte beispielsweise die Datei Heimautomation.php mit Hilfe WinSCP direkt ins Verzeichnis kopieren. In WinSCP bin ich als pi angemeldet und sobald die Datei www-data gehört, gibt es Probleme, obwohl pi zur Gruppe www-data gehört. Ich kann die Datei entweder löschen und sie dann rüberkopieren, dann muss ich aber die Rechte ändern, oder ich ändere kurzzeitig den Besitzer. Das muss doch komfortabler gehen. Eventuell mache ich hier auch wieder was grundlegendes falsch.

Jetzt mitmachen!

Du hast noch kein Benutzerkonto auf unserer Seite? Registriere dich kostenlos und nimm an unserer Community teil!