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.
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
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,
$directory
et les affecter à dirVals
,$wantedUser
à gMember
ET $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é!
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).
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.
Une possibilité amusante (mais ce n'est plus bash) est de créer un programme C avec le drapeau suid, propriété de mysql.
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;
}
Compiler:
gcc -Wall -ocaniwrite caniwrite.c
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.
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!'
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
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 :-)