web-dev-qa-db-fra.com

Tester 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 en cours:

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

Mais quelqu'un peut-il suggérer un moyen de tester si un répertoire est accessible en écriture par certains autre uid?

Mon scénario est que j'administre une instance de serveur MySQL et que je souhaite modifier temporairement l'emplacement du fichier journal de requête lente. Je peux le faire en exécutant une commande MySQL SET GLOBAL slow_query_log_file='$new_log_filename', puis désactivez et activez la journalisation des requêtes pour que mysqld commence à utiliser ce fichier.

Mais j'aimerais que mon script vérifie que l'uid du processus mysqld dispose des autorisations pour créer ce nouveau fichier journal. Je voudrais donc faire quelque chose comme (pseudocode):

$ 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 repose pas sur su car je ne peux pas supposer que l'utilisateur de mon script a un privilège su.

43
Bill Karwin

Voici une longue façon détournée 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
28
chepner

Cela pourrait faire le test:

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 n'y a que n test (si), pas de boucle et pas de fourche.

+ Nota: comme je l'ai utilisé stat -Lc au lieu de stat -c, cela fonctionnera aussi pour les liens symboliques!

La condition est donc si,

  • J'ai pu lire avec succès les statistiques de $directory et les affecter à dirVals,
  • Et (
    • (Correspondance propriétaire Et L'indicateur UserWriteable est présent)
    • o drapeau Autre inscriptible est présent
    • o (Flag GroupWriteabe est présent ET
      • Je pourrais réussir la liste des membres de $wantedUser à gMember ET
      • Une chaîne construite en fusionnant les champs 2 au dernier de $gMember correspondra beginOfSting-Ou-quelque-chose-suivi-par-un-espace , immédiatement suivi par le groupe cible (${dirVals[1]}), immédiatement suivi de un-espace-suivi-de-quelque chose-Ou-endOfString . )

alors echo Bonne année!

Comme le test du groupe implique une seconde fourchette (Et j'aime réduire autant que possible ces appels), c'est le dernier test à faire.

Ancien:

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

Nota: Avertir de joindre d'abord des guillemets doubles pour avoir $directory développé!

9
F. Hauri

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 bien sûr besoin des privilèges Sudo.

Si vous avez explicitement besoin de l'uid au lieu du nom d'utilisateur, vous pouvez également 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. Remplacez votre propre valeur si nécessaire.

[~ # ~] mise à jour [~ # ~] (pour prendre en charge les utilisateurs non privilégiés de Sudo)
Pour obtenir un script bash pour changer d'utilisateurs sans sudu, il faudrait avoir la capacité de suid ("changer d'ID utilisateur"). Comme indiqué par cette réponse , il s'agit d'une restriction de sécurité qui nécessite un hack pour contourner. Consultez ce blog pour un exemple de "comment" le contourner (je ne l'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 autorisé à suid (essayez chmod 4755 file-name). Ensuite, vous pouvez appeler setuid(#) à partir du script C pour définir l'ID de l'utilisateur actuel et soit continuer l'exécution de code à partir de l'application C, soit lui faire exécuter un script bash distinct qui exécute les commandes dont vous avez besoin/souhaitez. 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).

7
newfurniturey

Parce que j'ai dû apporter quelques modifications à la réponse de @ chepner pour que cela fonctionne, je poste mon script ad hoc ici pour un copier-coller facile. C'est une refactorisation mineure seulement, et j'ai revoté la réponse de chepner. Je supprimerai la mienne si la réponse acceptée est mise à jour avec ces correctifs. J'ai déjà laissé des commentaires sur cette réponse en soulignant les choses avec lesquelles j'ai eu des problèmes.

Je voulais supprimer les bashismes, c'est pourquoi je n'utilise pas du tout de tableaux. Le ((évaluation arithmétique)) est toujours une fonctionnalité uniquement Bash, donc je suis coincé sur Bash après tout.

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

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

5
tripleee

Une possibilité amusante (mais ce n'est plus bash) est de créer un programme C avec le drapeau suid, propriété de mysql.

Étape 1.

Créez ce merveilleux fichier source C et appelez-le caniwrite.c (désolé, j'ai toujours eu du mal à choisir 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 le dossier de votre choix, /usr/local/bin/ étant un bon choix, changez de propriétaire et définissez 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

Terminé!

Appelez-le simplement comme:

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

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

3
gniourf_gniourf

alias wbyu='_(){ local -i FND=0; if [[ $# -eq 2 ]]; then for each in $(groups "$1" | awk "{\$1=\"\";\$2=\"\"; print \$0}"); do (($(find "${2}" \( -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 second est le répertoire à vérifier. Il recherche les autorisations accessibles en écriture par n'importe qui et parcourt également les groupes de l'utilisateur spécifié pour vérifier si le répertoire est dans le groupe d'utilisateurs et accessible en écriture - si l'un ou l'autre obtient un hit, il définit un indicateur trouvé et imprime Eureka! à la fin.

IOW:

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

Pourquoi ne pas simplement faire quelque chose de simple comme ESSAYER un mkdir sur le dossier en question. C'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
Mike Q

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

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

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

  local file_access=$(stat -c "%a" $2)
  local file_group_access=${file_access:1:1}
  local file_group_name=$(stat -c "%G" $2)
  local user_group_list=$(groups $1 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 $2)"
  echo "User is:" $(groups $1 2>/dev/null)
  echo "User" $1 "can write to" $2 ":" $(can_user_write_to_file $1 $2)
  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 compte tenu de la propriété du fichier et de l'accès en écriture de groupe :-)

2
skybert