TL; DR
find / ! -type l -print0 |
sudo -u "$user" perl -Mfiletest=access -l -0ne 'print if -w'
Debe preguntar al sistema si el usuario tiene permiso de escritura. La única forma confiable es cambiar el uid efectivo, el gid efectivo y los gids de suplementación a los del usuario y usar la access(W_OK)llamada al sistema (incluso eso tiene algunas limitaciones en algunos sistemas / configuraciones).
Y tenga en cuenta que no tener permiso de escritura en un archivo no garantiza necesariamente que no pueda modificar el contenido del archivo en esa ruta.
La historia mas larga
Vamos a considerar lo que se necesita, por ejemplo, para un usuario $ a tener acceso de escritura /foo/file.txt(suponiendo que ninguno de /fooy /foo/file.txtson enlaces simbólicos)?
El necesita:
- buscar acceso a
/(no es necesario read)
- buscar acceso a
/foo(no es necesario read)
- acceso de escritura a
/foo/file.txt
Ya puede ver que los enfoques (como @ lcd047 o @ apaul's ) que verifican solo el permiso de file.txtno funcionarán porque podrían decir que file.txtse puede escribir incluso si el usuario no tiene permiso de búsqueda para /o /foo.
Y un enfoque como:
sudo -u "$user" find / -writeble
Tampoco funcionará porque no informará los archivos en directorios en los que el usuario no tiene acceso de lectura (ya findque $userno puede enumerar su contenido) incluso si puede escribir en ellos.
Si nos olvidamos de las ACL, los sistemas de archivos de solo lectura, los indicadores FS (como inmutables), otras medidas de seguridad (apparmor, SELinux, que incluso pueden distinguir entre diferentes tipos de escritura) y solo se centran en los permisos tradicionales y los atributos de propiedad, para obtener un dado (buscar o escribir) permiso, eso ya es bastante complicado y difícil de expresar find.
Necesitas:
- si el archivo es de su propiedad, necesita ese permiso para el propietario (o tiene uid 0)
- si el archivo no es de su propiedad, pero el grupo es uno de los suyos, entonces necesita ese permiso para el grupo (o tiene uid 0).
- si no es de su propiedad y no pertenece a ninguno de sus grupos, se aplican los otros permisos (a menos que su uid sea 0).
En findsintaxis, aquí como ejemplo con un usuario de uid 1 y gids 1 y 2, eso sería:
find / -type d \
\( \
-user 1 \( -perm -u=x -o -prune \) -o \
\( -group 1 -o -group 2 \) \( -perm -g=x -o -prune \) -o \
-perm -o=x -o -prune \
\) -o -type l -o \
-user 1 \( ! -perm -u=w -o -print \) -o \
\( -group 1 -o -group 2 \) \( ! -perm -g=w -o -print \) -o \
! -perm -o=w -o -print
Que uno ciruelas los directorios que usuario no tiene derecho a buscar y para otros tipos de archivos (enlaces simbólicos excluidos ya que no son relevantes), controles para el acceso de escritura.
Si también desea considerar el acceso de escritura a directorios:
find / -type d \
\( \
-user 1 \( -perm -u=x -o -prune \) -o \
\( -group 1 -o -group 2 \) \( -perm -g=x -o -prune \) -o \
-perm -o=x -o -prune \
\) ! -type d -o -type l -o \
-user 1 \( ! -perm -u=w -o -print \) -o \
\( -group 1 -o -group 2 \) \( ! -perm -g=w -o -print \) -o \
! -perm -o=w -o -print
O para una $usermembresía arbitraria y de grupo recuperada de la base de datos de usuarios:
groups=$(id -G "$user" | sed 's/ / -o -group /g'); IFS=" "
find / -type d \
\( \
-user "$user" \( -perm -u=x -o -prune \) -o \
\( -group $groups \) \( -perm -g=x -o -prune \) -o \
-perm -o=x -o -prune \
\) ! -type d -o -type l -o \
-user "$user" \( ! -perm -u=w -o -print \) -o \
\( -group $groups \) \( ! -perm -g=w -o -print \) -o \
! -perm -o=w -o -print
(que es 3 procesos en total: id, sedy find)
Lo mejor aquí sería descender el árbol como raíz y verificar los permisos como usuario para cada archivo.
find / ! -type l -exec sudo -u "$user" sh -c '
for file do
[ -w "$file" ] && printf "%s\n" "$file"
done' sh {} +
(ese es un findproceso más uno sudoy shprocesa cada pocos miles de archivos, [y printfgeneralmente se construyen en el shell).
O con perl:
find / ! -type l -print0 |
sudo -u "$user" perl -Mfiletest=access -l -0ne 'print if -w'
(3 procesos en total: find, sudoy perl).
O con zsh:
files=(/**/*(D^@))
USERNAME=$user
for f ($files) {
[ -w $f ] && print -r -- $f
}
(0 procesos en total, pero almacena la lista completa de archivos en la memoria)
Esas soluciones se basan en la access(2)llamada al sistema. Es decir, en lugar de reproducir el algoritmo que usa el sistema para verificar el permiso de acceso, le pedimos al sistema que haga esa verificación con el mismo algoritmo (que tiene en cuenta los permisos, las ACL, los indicadores inmutables, los sistemas de archivos de solo lectura ... ) lo usaría si intentas abrir el archivo para escribir, por lo que es lo más cercano a una solución confiable.
Para probar las soluciones dadas aquí, con las diversas combinaciones de usuario, grupo y permisos, puede hacer:
perl -e '
for $u (1,2) {
for $g (1,2,3) {
$d1="u${u}g$g"; mkdir$d1;
for $m (0..511) {
$d2=$d1.sprintf"/%03o",$m; mkdir $d2; chown $u, $g, $d2; chmod $m,$d2;
for $uu (1,2) {
for $gg (1,2,3) {
$d3="$d2/u${uu}g$gg"; mkdir $d3;
for $mm (0..511) {
$f=$d3.sprintf"/%03o",$mm;
open F, ">","$f"; close F;
chown $uu, $gg, $f; chmod $mm, $f
}
}
}
}
}
}'
Variar el usuario entre 1 y 2 y el grupo entre 1, 2 y 3 y limitarnos a los 9 bits inferiores de los permisos, ya que son 9458694 archivos creados. Eso para directorios y luego nuevamente para archivos.
Eso crea todas las combinaciones posibles de u<x>g<y>/<mode1>/u<z>g<w>/<mode2>. El usuario con uid 1 y gid 1 y 2 tendría acceso de escritura, u2g1/010/u2g3/777pero no u1g2/677/u1g1/777por ejemplo.
Ahora, todas esas soluciones intentan identificar las rutas de los archivos que el usuario puede abrir para escribir, eso es diferente de las rutas en las que el usuario puede modificar el contenido. Para responder a esa pregunta más genérica, hay varias cosas a tener en cuenta:
- $ user puede no tener acceso de escritura,
/a/b/filepero si es el propietario file(y tiene acceso de búsqueda /a/b, y el sistema de archivos no es de solo lectura, y el archivo no tiene el indicador inmutable, y tiene acceso de shell al sistema), entonces él podría cambiar los permisos del filey otorgarse acceso.
- Lo mismo si posee
/a/bpero no tiene acceso de búsqueda.
- $ usuario no puede tener acceso a
/a/b/fileporque no tiene acceso a la búsqueda /ao /a/b, pero ese archivo puede tener un enlace duro en /b/c/file, por ejemplo, en cuyo caso puede ser capaz de modificar el contenido de /a/b/fileabriéndolo a través de su /b/c/filetrayectoria.
- Lo mismo con los soportes de unión . Es posible que no tenga acceso de búsqueda
/a, pero /a/bpuede estar montado en un enlace/c , por lo que podría abrir filepara escribir a través de su /c/fileotra ruta.
- Es posible que no tenga permisos de escritura
/a/b/file, pero si tiene acceso de escritura /a/bpuede eliminar o cambiar el nombre fileallí y reemplazarlo con su propia versión. Cambiaría el contenido del archivo /a/b/fileincluso si fuera un archivo diferente.
- Lo mismo si él tiene acceso de escritura
/a(que podría cambiar el nombre /a/ba /a/c, crear un nuevo /a/bdirectorio y un nuevo fileen ella.
Para encontrar las rutas que $userpodrían modificarse. Para abordar 1 o 2, ya no podemos confiar en la access(2)llamada al sistema. Podríamos ajustar nuestro find -permenfoque para asumir el acceso de búsqueda a los directorios, o escribir el acceso a los archivos tan pronto como usted sea el propietario:
groups=$(id -G "$user" | sed 's/ / -o -group /g'); IFS=" "
find / -type d \
\( \
-user "$user" -o \
\( -group $groups \) \( -perm -g=x -o -prune \) -o \
-perm -o=x -o -prune \
\) ! -type d -o -type l -o \
-user "$user" -print -o \
\( -group $groups \) \( ! -perm -g=w -o -print \) -o \
! -perm -o=w -o -print
Podríamos abordar 3 y 4, registrando el dispositivo y los números de inodo o todos los archivos que $ user tiene permiso para escribir e informar todas las rutas de archivos que tienen esos números dev + inode. Esta vez, podemos usar los access(2)enfoques basados en más confiables :
Algo como:
find / ! -type l -print0 |
sudo -u "$user" perl -Mfiletest=access -0lne 'print 0+-w,$_' |
perl -l -0ne '
($w,$p) = /(.)(.*)/;
($dev,$ino) = stat$p or next;
$writable{"$dev,$ino"} = 1 if $w;
push @{$p{"$dev,$ino"}}, $p;
END {
for $i (keys %writable) {
for $p (@{$p{$i}}) {
print $p;
}
}
}'
5 y 6 son a primera vista complicados por el tpoco de los permisos. Cuando se aplica en los directorios, ese es el bit de eliminación restringido que impide que los usuarios (que no sean el propietario del directorio) eliminen o renombren los archivos que no poseen (aunque tengan acceso de escritura al directorio).
Por ejemplo, si volvemos a nuestro ejemplo anterior, si tiene acceso de escritura /a, entonces usted debería ser capaz de cambiar el nombre /a/bde /a/c, y volver a crear un /a/bdirectorio y un nuevo fileallí. Pero si el tbit está activado /ay usted no posee /a, entonces solo puede hacerlo si posee /a/b. Eso da:
- Si posee un directorio, según 1, puede otorgarse acceso de escritura, y el bit t no se aplica (y podría eliminarlo de todos modos), por lo que puede eliminar / renombrar / recrear cualquier archivo o directorio allí, por lo que todas las rutas de archivos que se encuentran debajo son suyas para reescribir con cualquier contenido.
- Si no lo posee pero tiene acceso de escritura, entonces:
- O bien el
tbit no está configurado, y usted está en el mismo caso que el anterior (todas las rutas de archivos son suyas).
- o está configurado y luego no puede modificar los archivos que no posee o no tiene acceso de escritura, por lo que para nuestro propósito de encontrar las rutas de archivos que puede modificar, es lo mismo que no tener permiso de escritura.
Entonces podemos abordar todos los 1, 2, 5 y 6 con:
find / -type d \
\( \
-user "$user" -prune -exec find {} + -o \
\( -group $groups \) \( -perm -g=x -o -prune \) -o \
-perm -o=x -o -prune \
\) ! -type d -o -type l -o \
-user "$user" \( -type d -o -print \) -o \
\( -group $groups \) \( ! -perm -g=w -o \
-type d ! -perm -1000 -exec find {} + -o -print \) -o \
! -perm -o=w -o \
-type d ! -perm -1000 -exec find {} + -o \
-print
Eso y la solución para 3 y 4 son independientes, puede fusionar su salida para obtener una lista completa:
{
find / ! -type l -print0 |
sudo -u "$user" perl -Mfiletest=access -0lne 'print 0+-w,$_' |
perl -0lne '
($w,$p) = /(.)(.*)/;
($dev,$ino) = stat$p or next;
$writable{"$dev,$ino"} = 1 if $w;
push @{$p{"$dev,$ino"}}, $p;
END {
for $i (keys %writable) {
for $p (@{$p{$i}}) {
print $p;
}
}
}'
find / -type d \
\( \
-user "$user" -prune -exec sh -c 'exec find "$@" -print0' sh {} + -o \
\( -group $groups \) \( -perm -g=x -o -prune \) -o \
-perm -o=x -o -prune \
\) ! -type d -o -type l -o \
-user "$user" \( -type d -o -print0 \) -o \
\( -group $groups \) \( ! -perm -g=w -o \
-type d ! -perm -1000 -exec sh -c 'exec find "$@" -print0' sh {} + -o -print0 \) -o \
! -perm -o=w -o \
-type d ! -perm -1000 -exec sh -c 'exec find "$@" -print0' sh {} + -o \
-print0
} | perl -l -0ne 'print unless $seen{$_}++'
Como debe quedar claro si ha leído todo hasta ahora, parte de él al menos solo trata con los permisos y la propiedad, no con las otras características que pueden otorgar o restringir el acceso de escritura (FS de solo lectura, ACL, indicador inmutable, otras características de seguridad ...) Y a medida que lo procesamos en varias etapas, parte de esa información puede ser incorrecta si los archivos / directorios se crean / eliminan / cambian de nombre o se modifican sus permisos / propiedad mientras se ejecuta ese script, como en un servidor de archivos ocupado con millones de archivos .
Notas de portabilidad
Todo ese código es estándar (POSIX, Unix para tbit) excepto:
-print0es una extensión de GNU que ahora también es compatible con algunas otras implementaciones. Con findimplementaciones que carecen de soporte para ello, puede usar -exec printf '%s\0' {} +en su lugar y reemplazar -exec sh -c 'exec find "$@" -print0' sh {} +con -exec sh -c 'exec find "$@" -exec printf "%s\0" {\} +' sh {} +.
perlno es un comando especificado por POSIX pero está ampliamente disponible. Necesitas perl-5.6.0o más para -Mfiletest=access.
zshno es un comando especificado por POSIX. Ese zshcódigo anterior debería funcionar con zsh-3 (1995) y superior.
sudono es un comando especificado por POSIX. El código debería funcionar con cualquier versión siempre que la configuración del sistema permita la ejecución perldel usuario dado.