Test de Bash si un répertoire est accessible en écriture par un UID donné?

nous pouvons tester si un répertoire est accessible en écriture par l'uid du processus courant:

$ if [ -w $directory ] ; then echo 'Eureka!' ; fi

mais est-ce que quelqu'un peut suggérer un moyen de tester si un répertoire est accessible en écriture par quelque autre uid?

mon scénario est que j'administre une instance de serveur MySQL, et je veux changer l'emplacement du fichier journal de requête lente Temporairement. Je peux le faire en exécutant une commande MySQL SET GLOBAL slow_query_log_file='$new_log_filename' et puis désactiver et activer la requête journalisation pour faire mysqld démarrer en utilisant ce fichier.

mais j'aimerais que mon script vérifie que l'uid du processus mysqld a les permissions pour créer ce nouveau fichier log. Donc j'aimerais faire quelque chose comme (pseudo):

$ if [ -w-as-mysql-uid `basename $new_log_filename` ] ; then echo 'Eureka!' ; fi

mais bien sûr, c'est un prédicat de test imaginaire.

Clarification: je voudrais une solution qui ne s'appuie pas sur su parce que je ne peux pas supposer la l'utilisateur de mon script a le privilège su.

38
demandé sur Bill Karwin 2012-12-31 20:20:01

8 réponses

Voici une longue façon de vérifier.

USER=johndoe
DIR=/path/to/somewhere

# Use -L to get information about the target of a symlink,
# not the link itself, as pointed out in the comments
INFO=( $(stat -L -c "%a %G %U" $DIR) )
PERM=${INFO[0]}
GROUP=${INFO[1]}
OWNER=${INFO[2]}

ACCESS=no
if (( ($PERM & 0002) != 0 )); then
    # Everyone has write access
    ACCESS=yes
elif (( ($PERM & 0020) != 0 )); then
    # Some group has write access.
    # Is user in that group?
    gs=( $(groups $USER) )
    for g in "${gs[@]}"; do
        if [[ $GROUP == $g ]]; then
            ACCESS=yes
            break
        fi
    done
elif (( ($PERM & 0200) != 0 )); then
    # The owner has write access.
    # Does the user own the file?
    [[ $USER == $OWNER ]] && ACCESS=yes
fi
25
répondu chepner 2018-02-27 10:59:25

qui pourrait faire l'essai:

if read -a dirVals < <(stat -Lc "%U %G %A" $directory) && (
    ( [ "$dirVals" == "$wantedUser" ] && [ "${dirVals[2]:2:1}" == "w" ] ) ||
    ( [ "${dirVals[2]:8:1}" == "w" ] ) ||
    ( [ "${dirVals[2]:5:1}" == "w" ] && (
        gMember=($(groups $wantedUser)) &&
        [[ "${gMember[*]:2}" =~ ^(.* |)${dirVals[1]}( .*|)$ ]]
    ) ) )
  then
    echo 'Happy new year!!!'
  fi

explications:

Il y a seulement un test (si), pas de boucle et pas de fourche .

+ Note: Comme j'ai utilisé stat -Lc au lieu de stat -c , cela fonctionnera pour symlinks aussi!

Si la condition est si ,

  • je pourrais lire avec succès les statistiques de $directory et les assigner à dirVals ,
  • et (
    • ( Propriétaire match Et Drapeau UserWriteable est présent )
    • ou drapeau Autres droits en Écriture est présent
    • ou ( Drapeau GroupWriteabe est actuellement et "
      • j'ai pu avec succès assing de la liste des membres de $wantedUser à gMember ET
      • une chaîne construite par fusion des Champs 2 à dernier de $gMember correspondra à beginOfSting-or-something-follow-by-a-space , immédiatement suivi par le groupe de cible ( ${dirVals[1]} ), immédiatement suivi de a-space-follow-by-something-or-endOfString . )

puis echo Bonne année!

comme le test du groupe implie un second fork (et j'aime réduire autant que possible de tels appels), c'est le dernier test à faire.

Vieux :

simplement:

su - mysql -c "test -w '$directory'" && echo yes
yes

ou:

if su - mysql -s /bin/sh -c "test -w '$directory'" ; then 
    echo 'Eureka!'
  fi

Note: mettre en garde de placer d'abord des guillemets pour avoir $directory développé!

8
répondu F. Hauri 2013-01-09 15:56:28

Vous pouvez utiliser sudo pour exécuter le test dans votre script. Par exemple:

sudo -u mysql -H sh -c "if [ -w $directory ] ; then echo 'Eureka' ; fi"

pour ce faire, l'utilisateur exécutant le script aura besoin des privilèges sudo bien sûr.

si vous avez explicitement besoin de l'uid à la place du nom d'utilisateur, vous pouvez aussi utiliser:

sudo -u \#42 -H sh -c "if [ -w $directory ] ; then echo 'Eureka' ; fi"

dans ce cas, 42 est l'uid de l'utilisateur mysql . Substituez votre propre valeur si nécessaire.

mise à JOUR (à l'appui des non-sudo-les utilisateurs privilégiée)

Pour obtenir un script bash à modifier-utilisateurs sans sudu serait d'exiger la capacité de suid ("switch user id"). Ceci, comme souligné par cette réponse , est une restriction de sécurité qui nécessite un piratage pour travailler autour. Consultez ce blog pour un exemple de "comment" travailler autour de lui (je n'ai pas testé/essayé, donc je ne peux pas confirmer son succès).

ma recommandation, si possible, serait d'écrire un script en C qui est donné la permission à suid (essayer chmod 4755 file-name ). Ensuite, vous pouvez appeler setuid(#) à partir du script C pour définir l'id de l'utilisateur courant et soit continuer l'exécution de code à partir de L'application C, ou lui faire exécuter un script bash séparé qui exécute toutes les commandes dont vous avez besoin/voulez. C'est aussi une méthode assez hacky, mais en ce qui concerne les alternatives non-sudo c'est probablement l'une des plus faciles (à mon avis).

5
répondu newfurniturey 2017-05-23 11:47:05

une drôle de possibilité (mais ce n'est plus bash) est de faire un programme C avec le drapeau suid, propriété de mysql.

Étape 1.

Créez ce merveilleux fichier c source, et appelez-le caniwrite.c (désolé, j'ai toujours été nul au choix des noms):

#define _GNU_SOURCE
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main(int argc,char* argv[]) {
   int i;
   for(i=1;i<argc;++i) {
      if(eaccess(argv[i],W_OK)) {
         return EXIT_FAILURE;
      }
   }
   return EXIT_SUCCESS;
}

Étape 2.

compiler:

gcc -Wall -ocaniwrite caniwrite.c

Étape 3.

déplacez-le dans n'importe quel dossier que vous aimez, /usr/local/bin/ étant un bon choix, changez sa propriété et mettez le drapeau suid: (faites ceci en tant que root)

# mv -nv caniwrite /usr/local/bin
# chown mysql:mysql /usr/local/bin/caniwrite
# chmod +s /usr/local/bin/caniwrite

fait!

appelez ça comme:

if caniwrite folder1; then
    echo "folder1 is writable"
else
    echo "folder1 is not writable"
fi

En fait, vous pouvez l'appeler caniwrite avec autant d'arguments que vous le souhaitez. Si tous les répertoires (ou les fichiers) sont accessibles en écriture, alors le code de retour est vrai, sinon le code de retour est faux.

3
répondu gniourf_gniourf 2012-12-31 17:54:13

j'ai écrit une fonction can_user_write_to_file qui retournera 1 si l'utilisateur lui est passé soit est le propriétaire du fichier/répertoire, ou est membre d'un groupe qui a accès en écriture à ce fichier/répertoire. Sinon, la méthode retourne 0 .

## Method which returns 1 if the user can write to the file or
## directory.
##
##  :: user name
##  :: file
function can_user_write_to_file() {
  if [[ $# -lt 2 || ! -r  ]]; then
    echo 0
    return
  fi

  local user_id=$(id -u  2>/dev/null)
  local file_owner_id=$(stat -c "%u" )
  if [[ ${user_id} == ${file_owner_id} ]]; then
    echo 1
    return
  fi

  local file_access=$(stat -c "%a" )
  local file_group_access=${file_access:1:1}
  local file_group_name=$(stat -c "%G" )
  local user_group_list=$(groups  2>/dev/null)

  if [ ${file_group_access} -ge 6 ]; then
    for el in ${user_group_list-nop}; do
      if [[ "${el}" == ${file_group_name} ]]; then
        echo 1
        return
      fi
    done
  fi

  echo 0
}

pour le tester, j'ai écrit une fonction de test wee:

function test_can_user_write_to_file() {
  echo "The file is: $(ls -l )"
  echo "User is:" $(groups  2>/dev/null)
  echo "User"  "can write to"  ":" $(can_user_write_to_file  )
  echo ""
}

test_can_user_write_to_file root /etc/fstab
test_can_user_write_to_file invaliduser /etc/motd
test_can_user_write_to_file torstein /home/torstein/.xsession
test_can_user_write_to_file torstein /tmp/file-with-only-group-write-access

au moins à partir de ces tests, la méthode fonctionne comme prévu en tenant compte de la propriété du fichier et de l'accès en écriture de groupe :- )

2
répondu skybert 2013-01-17 16:11:19

parce que j'ai dû apporter quelques modifications à la réponse de @chepner pour la faire fonctionner, je poste mon script ad-hoc ici pour facile copier & coller. C'est un remaniement mineur seulement, et j'ai voté la réponse de chepner. Je supprimerai la mienne si la réponse acceptée est mise à jour avec ces corrections. J'ai déjà laissé des commentaires sur cette réponse soulignant les choses qui m'ont posé problème.

je voulais en finir avec les Bashismes, c'est pour ça que je n'utilise pas de tableaux. Le (( évaluation arithmétique )) est toujours une caractéristique de Bash-only, donc je suis coincé sur Bash après tout.

for f; do
    set -- $(stat -Lc "0%a %G %U" "$f")
    (("" & 0002)) && continue
    if (("" & 0020)); then
        case " "$(groups "$USER")" " in *" "" "*) continue ;; esac
    elif (("" & 0200)); then
        [ "" = "$USER" ] && continue
    fi
    echo ""151900920": Wrong permissions" "$@" "$f" >&2
done

Sans les commentaires, c'est même assez compact.

2
répondu tripleee 2014-04-28 17:34:23

pourquoi ne pas simplement faire quelque chose de simple comme essayer un mkdir sur le dossier en question. Il est plus fiable....

    mkdir your_directory/
    [[ $? -ne 0 ]] && echo "fatal" || echo "winner winner chicken dinner.."

ou ?

    # -- run the following commands as the_User_ID
    sudo su - the_User_ID << BASH

    mkdir your_directory/
    [[ $? -ne 0 ]] && echo "fatal" || echo "winner winner chicken dinner.."

    BASH
2
répondu Mike Q 2014-07-18 19:26:14

alias wbyu='_(){ local -i FND=0; if [[ $# -eq 2 ]]; then for each in $(groups "" | awk "{$1=\"\";$2=\"\"; print $0}"); do (($(find "" \( -perm /220 -o -group "$each" -a -perm /g+w \) 2>/dev/null | wc -l))) && FND=1; done; else echo "Usage: wbyu <user> <file|dir>"; fi; (($FND)) && echo "Eureka!"; }; _'

je l'ai mis dans un alias, il prend deux arguments, le premier est l'utilisateur et le deuxième est le répertoire à vérifier. Il recherche les permissions accessibles en écriture par n'importe qui et boucle également sur les groupes de l'utilisateur spécifié pour vérifier si le répertoire est dans le groupe d'utilisateur et accessible en écriture - si l'un ou l'autre obtient un coup il définit un drapeau trouvé et imprime Eureka! à la fin.

IOW:

FND=0
USER=user1
DIR=/tmp/test
for each in $(groups "$USER" | awk '{="";=""; print "151900920"}'); do 
(($(find "$DIR" \( -perm /220 -o -group "$each" -a -perm /g+w \)\ 
   2>/dev/null | wc -l))) && FND=1 
done
(($FND)) && echo 'Eureka!'
2
répondu 2015-01-17 05:53:41