【问题标题】:Test if a directory is writable by a given UID?测试一个目录是否可以被给定的 UID 写入?
【发布时间】:2012-12-15 17:50:18
【问题描述】:

我们可以通过当前进程的uid来测试一个目录是否可写:

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

但是任何人都可以建议一种方法来测试一个目录是否可以被某些 other uid 写入?

我的场景是我正在管理一个 MySQL 服务器实例,我想临时更改慢查询日志文件的位置。我可以通过执行 MySQL 命令 SET GLOBAL slow_query_log_file='$new_log_filename' 来做到这一点,然后禁用和启用查询日志记录以使 mysqld 开始使用该文件。

但我希望我的脚本检查mysqld 进程的uid 是否有权创建新的日志文件。所以我想做一些类似(伪代码)的事情:

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

但这当然是一个虚构的测试谓词。

澄清:我想要一个不依赖 su 的解决方案,因为我不能假设我的脚本的用户具有 su 权限。

【问题讨论】:

  • 有趣的是,OS X 上的 Server Admin.app 有一个“权限检查器”,可以将用户拖放到上面,然后选择任何文件,它会显示用户可以和不能的所有文件操作做。但是,不知道它是如何工作的。你会认为这很容易。
  • 你真的需要这么通用的解决方案吗?为什么不要求该目录由 mysql 拥有并具有所有者写入权限,然后只检查该特定情况?
  • @Bamar,我正在尝试开发一个 MySQL 社区普遍使用的工具,并不是每个人都以字面用户“mysql”运行 MySQL(即使他们应该这样做)。

标签: bash directory file-permissions


【解决方案1】:

这可以做测试:

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

解释:

只有一个测试(如果),没有循环没有分叉

+注意:因为我使用了stat -Lc 而不是stat -c,这也适用于符号链接!

所以条件是如果

  • 我可以成功读取$directory 的统计数据并将它们分配给dirVals
  • (
    • (所有者匹配并且标志 UserWriteable 存在)
    • 标志其他可写存在
    • (标志 GroupWriteabe 存在 AND
      • 我可以成功地将$wantedUser 的成员列表分配给gMember AND
      • 通过将字段 2 合并到 $gMember 的最后一个构建的字符串将匹配 beginOfSting-Or-something-followed-by-a-space,紧随其后的是目标组 (${dirVals[1]}),紧随其后的是 a-space-followed-by-something-Or-endOfString。 )

那么回声新年快乐!

由于小组的测试意味着第二次 fork(我喜欢尽可能减少此类调用),因此这是要完成的最后一个测试。

简单地说:

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

或:

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

注意:警告要先用双引号括起来,因为 $directory 已开发!

【讨论】:

  • +1 是的,这会起作用,所以这是一个正确的答案,但我希望避免su,因为我不能假设用户具有 su 权限。
  • 看看stat -c "%U %G %a" $directorystat -c "%u %g %A" $directory 或任何变体...新年快乐!
  • ACL 怎么样?而且,$wantedGroup 不需要遍历所有$wantedUser 所在的组吗?
  • @PhilFrost 抱歉,因为过年,压力很大……还有更好的作品。
  • +1 很酷,尽管您应该检查用户、组、其他顺序,而不是用户、其他、组,因为文件的“其他”权限比“组”权限多(非常罕见,我知道)会失败。例如:touch foo; chmod 044; [ -w foo ] || echo "I can't write to a file where I don't have permissions but others have"
【解决方案2】:

您可以使用sudo 在您的脚本中执行测试。例如:

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

为此,执行脚本的用户当然需要sudo 权限。

如果你明确需要 uid 而不是用户名,你也可以使用:

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

在这种情况下,42mysql 用户的 uid。如果需要,替换您自己的值。

更新(以支持非 sudo 特权用户)
要在没有sudu 的情况下获取 bash 脚本来更改用户,则需要能够suid(“切换用户 ID”)。正如this answer 所指出的,这是一种安全限制,需要破解才能解决。检查this blog 以获取“如何”解决它的示例(我没有测试/尝试过它,所以我无法确认它是否成功)。

如果可能的话,我的建议是用 C 语言编写一个允许 suid 的脚本(试试chmod 4755 file-name)。然后,您可以从 C 脚本调用 setuid(#) 来设置当前用户的 id,然后继续从 C 应用程序执行代码,或者让它执行一个单独的 bash 脚本来运行您需要/想要的任何命令。这也是一种非常老套的方法,但就非 sudo 替代方法而言,它可能是最简单的方法之一(在我看来)。

【讨论】:

  • +1 是的,这会起作用,所以这是一个正确的答案,但我希望避免su,因为我不能假设用户具有 su 权限。
  • -H, --set-home 有什么用?
  • 我正在做类似 if ! sudo -u [App_xx] --shell [ -r "$path" -a -w "$path" ]; then ... 的事情,然后我可以以 root 身份执行操作/修复权限。
【解决方案3】:

这是一个漫长而迂回的检查方式。

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

【讨论】:

  • 谢谢,这很冗长,但它会起作用,而且它更适合不同的用户环境。
  • 警告符号链接,因为它们可能是 lrwxrwxrwx 而目标不是。
  • 我发现我不得不改变stat选项的顺序,stat -Lc "fmt" pro stat -cL
  • 最后stat -c '%a' 没有为我打印前导零,所以我不得不将其更改为'0%a' 以使算术正常工作。
【解决方案4】:

一个有趣的可能性(但它不再是 bash)是使用 mysql 拥有的 suid 标志制作一个 C 程序。

第 1 步。

创建这个美妙的 C 源文件,并将其命名为 caniwrite.c(抱歉,我一直不擅长选择名称):

#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;
}

第 2 步。

编译:

gcc -Wall -ocaniwrite caniwrite.c

第 3 步。

将它移动到您喜欢的任何文件夹中,/usr/local/bin/ 是一个不错的选择,更改它的所有权并设置 suid 标志:(以 root 身份执行此操作)

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

完成!

就叫它:

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

事实上,您可以使用任意数量的参数调用caniwrite。如果所有目录(或文件)都是可写的,则返回码为真,否则返回码为假。

【讨论】:

  • LOL 感谢您的建议,但将我的工具编写为 bash 脚本的目的是避免部署 C 代码! :-)
  • @BillKarwin 除此之外,我很确定这个问题没有 bash 解决方案。有一些技巧是可能的,但在 100% 的情况下都不会奏效(想想 ACL 和管理组的所有不同可能性)。现在,我给出的解决方案实际上并不是一个巨大的 C 代码部署!这是eaccess 调用的简单包装。这很有趣;-)
【解决方案5】:

我编写了一个函数can_user_write_to_file,如果传递给它的用户是文件/目录的所有者,或者是对该文件/目录具有写访问权限的组的成员,它将返回1。如果不是,则该方法返回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
}

为了测试它,我写了一个小测试函数:

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

至少从这些测试来看,考虑到文件所有权和组写访问权限,该方法按预期工作:-)

【讨论】:

    【解决方案6】:

    因为我必须对@chepner 的答案进行一些更改才能使其正常工作,所以我在此处发布我的临时脚本以便于复制和粘贴。这只是一个小的重构,我赞成 chepner 的回答。如果接受的答案通过这些修复更新,我将删除我的。我已经在那个答案上留下了 cmets,指出了我遇到的问题。

    我想取消 Bashisms,所以我根本不使用数组。 ((arithmetic evaluation)) 仍然是 Bash 独有的功能,所以我还是坚持使用 Bash。

    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
    

    没有 cmets,这甚至相当紧凑。

    【讨论】:

    • +1 感谢您的测试和解决方案!请不要删除。
    【解决方案7】:

    为什么不做一些简单的事情,比如在有问题的文件夹上尝试 mkdir。它更可靠....

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

    或者?

        # -- 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
    

    【讨论】:

    • 创造性的解决方案,但是我不需要知道当前进程的uid是否可以写入目录,我需要知道目录是否可以由不同的特定uid写入。
    • 您在顶部的问题似乎并没有推断出(在我看来)对不起混淆。我包括了一种以不同用户身份运行的方法。当然有几种方法可以做到这一点,例如让分叉的进程报告错误。
    • 谢谢,但需要 sudo 的解决方案不是我正在寻找的解决方案。在许多环境中,这是不允许的。
    • $? 的显式检查是多余的,尽管这是一种常见的反模式。任何看起来像command; [[ $? -ne 0 ]] &amp;&amp; failure || success 的东西都更好,写得更惯用command &amp;&amp; success || failure(假设success 本身总是成功)。
    【解决方案8】:

    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&gt;/dev/null | wc -l))) &amp;&amp; FND=1; done; else echo "Usage: wbyu &lt;user&gt; &lt;file|dir&gt;"; fi; (($FND)) &amp;&amp; echo "Eureka!"; }; _'

    我把它放到一个别名中,它有两个参数,第一个是用户,第二个是要检查的目录。它查找任何人都可写的权限,并循环遍历指定用户的组以检查目录是否在用户组中并且可写 - 如果有任何一个被命中,它会设置一个 found 标志并打印 Eureka!在末尾。

    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!'
    

    【讨论】:

    • 谢谢,这很有创意!
    • 别名似乎完全是多余的;只需调用函数wbyu 并取消别名。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-04-05
    • 2013-01-05
    • 2011-01-07
    • 2011-07-01
    • 2012-02-19
    • 1970-01-01
    • 2020-09-22
    相关资源
    最近更新 更多