bash 测试一个目录是否可以被给定的 UID 写入?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/14103806/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-09 23:09:01  来源:igfitidea点击:

Test if a directory is writable by a given UID?

bashdirectoryfile-permissions

提问by Bill Karwin

We can test if a directory is writable by the uid of the current process:

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

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

But can anyone suggest a way to test if a directory is writable by some otheruid?

但是任何人都可以建议一种方法来测试目录是否可由其他uid写入?

My scenario is that I am administering a MySQL Server instance, and I want to change the location of the slow-query log file temporarily. I can do this by executing a MySQL command SET GLOBAL slow_query_log_file='$new_log_filename'and then disable & enable query logging to make mysqldstart using that file.

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

But I'd like my script to check that the uid of the mysqldprocess has permissions to create that new log file. So I'd like to do something like (pseudocode):

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

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

But of course that's an imaginary test predicate.

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

Clarification:I would like a solution that doesn't rely on subecause I can't assume the user of my script has su privilege.

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

采纳答案by chepner

Here's a long, roundabout way of checking.

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

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

回答by F. Hauri

That could do the 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

Explanations:

说明:

There is only onetest (if), no loopand no fork.

只有一个测试(if),没有循环没有 fork

+Nota: as I'v used stat -Lcinstead of stat -c, this will work for symlinks too!

+注意:正如我使用的stat -Lc那样stat -c,这也适用于符号链接!

So condition is if,

所以条件是 如果

  • I could successfully read stats of $directoryand assign them to dirVals,
  • And(
    • ( Owner match AndFlag UserWriteable is present )
    • orflag Other Writeableis present
    • or( Flag GroupWriteabe is present AND
      • I could successfully assing member list of $wantedUserto gMemberAND
      • A string built by merging fields 2 to last of $gMemberwill match beginOfSting-Or-something-followed-by-a-space, immediately followed by target's group (${dirVals[1]}), immediately followed by a-space-followed-by-something-Or-endOfString. )
  • 我可以成功读取统计信息$directory并将它们分配给dirVals
  • 并且(
    • (所有者匹配并且存在标志 UserWriteable )
    • 标志其他可写的存在
    • ( 标志 GroupWriteabe 存在 并且
      • 我可以成功地将成员列表$wantedUser分配给gMemberAND
      • 通过将字段 2 合并到 last of 构建的字符串$gMember将匹配beginOfSting-Or-something-followed-by-a-space,紧接着是目标组 ( ${dirVals[1]}),紧接着是a-space-followed-by-something-Or-endOfString. )

thenecho Happy new year!

然后回声新年快乐!

As the group's test implie a second fork(And I love to reduce as possible such calls), this is the last test to be done.

由于该组的测试意味着要进行第二次分叉(而且我喜欢尽可能减少此类调用),因此这是要进行的最后一次测试。

Old:

:

Simply:

简单地:

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

or:

或者:

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

Nota:Warn to enclose first with double-quotes for having $directorydevelopped!

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

回答by newfurniturey

You can use sudoto execute the test in your script. For instance:

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

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

To do this, the user executing the script will need sudoprivileges of course.

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

If you explicitly need the uid instead of the username, you can also use:

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

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

In this case, 42is the uid of the mysqluser. Substitute your own value if needed.

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

UPDATE(to support non-sudo-priviledged users)
To get a bash script to change-users without suduwould be to require the ability to suid("switch user id"). This, as pointed out by this answer, is a security restriction that requires a hack to work around. Check this blogfor an example of "how to" work around it (I haven't tested/tried it, so I can't confirm it's success).

更新(以支持非 sudo 特权用户)
要获取 bash 脚本以更改用户,而无需sudu具有suid(“切换用户 ID”)的能力。正如这个答案所指出的那样,是一个安全限制,需要黑客来解决。查看此博客以获取“如何”解决问题的示例(我尚未测试/尝试过,因此无法确认它是否成功)。

My recommendation, if possible, would be to write a script in C that is given permission to suid (try chmod 4755 file-name). Then, you can call setuid(#)from the C script to set the current user's id and either continue code-execution from the C application, or have it execute a separate bash script that runs whatever commands you need/want. This is also a pretty hacky method, but as far as non-sudo alternatives it's probably one of the easiest (in my opinion).

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

回答by tripleee

Because I had to make some changes to @chepner's answer in order to get it to work, I'm posting my ad-hoc script here for easy copy & paste. It's a minor refactoring only, and I have upvoted chepner's answer. I'll delete mine if the accepted answer is updated with these fixes. I have already left comments on that answer pointing out the things I had trouble with.

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

I wanted to do away with the Bashisms so that's why I'm not using arrays at all. The ((arithmetic evaluation))is still a Bash-only feature, so I'm stuck on Bash after all.

我想废除 Bashisms,所以这就是我根本不使用数组的原因。在((算术评估))仍然是一个猛砸特有的功能,所以我毕竟停留在猛砸。

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 "
#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;
}
: Wrong permissions" "$@" "$f" >&2 done

Without the comments, this is even fairly compact.

没有评论,这甚至相当紧凑。

回答by gniourf_gniourf

One funny possibility (but it's not bash anymore) is to make a C program with the suid flag, owned by mysql.

一种有趣的可能性(但它不再是 bash)是使用 suid 标志创建一个 C 程序,该程序归 mysql 所有。

Step 1.

第1步。

Create this wonderful C source file, and call it caniwrite.c(sorry, I've always sucked at choosing names):

创建这个美妙的 C 源文件,并调用它caniwrite.c(抱歉,我总是不擅长选择名称):

gcc -Wall -ocaniwrite caniwrite.c

Step 2.

第2步。

Compile:

编译:

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

Step 3.

第 3 步。

Move it in whatever folder you like, /usr/local/bin/being a good choice, change it's ownership and set the suid flag: (do this as root)

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

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

Done!

完毕!

Just call it as:

只需将其称为:

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

In fact, you can call caniwritewith as many arguments as you wish. If allthe directories (or files) are writable, then the return code is true, otherwise the return code is false.

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

回答by skybert

I've written a function can_user_write_to_filewhich will return 1if the user passed to it either is the owner of the file/directory, or is member of a group which has write access to that file/directory. If not, the method returns 0.

我编写了一个函数can_user_write_to_file1如果传递给它的用户是文件/目录的所有者,或者是对该文件/目录具有写访问权限的组的成员,则该函数将返回。如果不是,则该方法返回0

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

To test it, I wrote a wee test function:

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

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

At least from these tests, the method works as intended considering file ownership and group write access :-)

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

回答by Mike Q

Why not just do something simple like TRY a mkdir on the folder in question. It's more reliable....

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

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

OR ?

或者 ?

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

回答by Mike Q

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!"; }; _'

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!"; }; _'

I put it into an alias it takes two arguments, the first is the user and the second is the directory to check. It looks for permissions writable by anyone and also loops over the groups of the specified user to check if the directory is in the user group and writable - if either gets a hit it sets a found flag and prints Eureka! at the end.

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

IOW:

爱荷华州:

##代码##