Bem, você sempre pode fazer:
#! /bin/bash -
{ shopt -s expand_aliases;SWITCH_TO_USER(){ { _u=$*;_x="$(declare;alias
shopt -p;set +o);"'set -- "${_a[@]}";unset _x _a';set +x;} 2>/dev/null
exec sudo -u "$1" env "_x=$_x" bash -c 'eval "$_x" 2> /dev/null;. "$0"
' "$0";};alias skip=":||:<<'SWITCH_TO_USER $_u'"
alias SWITCH_TO_USER="{ eval '"'_a=("$@")'"';} 2>/dev/null;SWITCH_TO_USER"
${_u+:} alias skip=:;} 2>/dev/null
skip
echo test
a=foo
set a b
SWITCH_TO_USER root
echo "$a and $1 as $(id -un)"
set -x
foo() { echo "bar as $(id -un)"; }
SWITCH_TO_USER rag
foo
set +x
SWITCH_TO_USER root again
echo "hi again from $(id -un)"
(ʘ‿ʘ)
Isso começou como uma piada, pois implementa o que é solicitado, embora provavelmente não exatamente como o esperado, e não é praticamente útil. Mas como ele evoluiu para algo que funciona até certo ponto e envolve alguns hacks legais, aqui está uma pequena explicação:
Como disse Miroslav , se deixarmos de lado os recursos no estilo Linux (que de qualquer forma não ajudariam aqui), a única maneira de um processo sem privilégios mudar o uid é executando um executável setuid.
Depois de obter o privilégio de superusuário (executando um executável setuid cujo proprietário é root, por exemplo), você pode alternar o ID do usuário efetivo entre o ID do usuário original, 0 e qualquer outro ID, a menos que você renuncie ao ID do usuário definido salvo ( como as coisas gostam sudoou sucostuma fazer).
Por exemplo:
$ sudo cp /usr/bin/env .
$ sudo chmod 4755 ./env
Agora eu tenho um envcomando que me permite executar qualquer comando com um ID de usuário efetivo e salvar o ID de usuário definido 0 (meu ID de usuário real ainda é 1000):
$ ./env id -u
0
$ ./env id -ru
1000
$ ./env -u PATH =perl -e '$>=1; system("id -u"); $>=0;$>=2; system("id -u");
$>=0; $>=$<=3; system("id -ru; id -u"); $>=0;$<=$>=4; system("id -ru; id -u")'
1
2
3
3
4
4
perlpossui wrappers para setuid/ seteuid(aqueles $>e $<variáveis).
O mesmo acontece com o zsh:
$ sudo zsh -c 'EUID=1; id -u; EUID=0; EUID=2; id -u'
1
2
Embora acima, esses idcomandos sejam chamados com um ID do usuário real e o conjunto salvo seja 0 (embora se eu tivesse usado o meu em ./envvez sudodisso, apenas o conjunto definido seria salvo, enquanto o ID do usuário real permaneceria 1000), o que significa que se fossem comandos não confiáveis, eles ainda poderiam causar algum dano; portanto, você gostaria de escrevê-lo como:
$ sudo zsh -c 'UID=1 id -u; UID=2 id -u'
(isto é, define todos os uids (conjunto efetivo, real e salvo) apenas para a execução desses comandos.
bashnão tem como alterar os IDs do usuário. Portanto, mesmo se você tivesse um executável setuid para chamar seu bashscript, isso não ajudaria.
Com bash, você fica executando um executável setuid toda vez que deseja alterar o uid.
A idéia no script acima é uma chamada para SWITCH_TO_USER, para executar uma nova instância do bash para executar o restante do script.
SWITCH_TO_USER someuseré mais ou menos uma função que executa o script novamente como um usuário diferente (usando sudo), mas pula o início do script até SWITCH_TO_USER someuser.
Onde fica complicado é que queremos manter o estado do bash atual depois de iniciar o novo bash como um usuário diferente.
Vamos dividir:
{ shopt -s expand_aliases;
Vamos precisar de pseudônimos. Um dos truques deste script é pular a parte do script até o SWITCH_TO_USER someuser, com algo como:
:||: << 'SWITCH_TO_USER someuser'
part to skip
SWITCH_TO_USER
Esse formulário é semelhante ao #if 0usado em C, que é uma maneira de comentar completamente algum código.
:é um no-op que retorna true. Então : || :, o segundo: nunca é executado. No entanto, é analisado. E << 'xxx'é uma forma de documento aqui, onde (porque xxxé citado), nenhuma expansão ou interpretação é feita.
Poderíamos ter feito:
: << 'SWITCH_TO_USER someuser'
part to skip
SWITCH_TO_USER
Mas isso significaria que o documento aqui teria que ser escrito e passado como padrão para : . :||:evita isso.
Agora, onde fica hacky é que usamos o fato de bashexpandir aliases muito cedo em seu processo de análise. Ter skipum alias para a :||: << 'SWITCH_TO_USER someuther'parte do construto comentando .
Vamos continuar:
SWITCH_TO_USER(){ { _u=$*;_x="$(declare;alias
shopt -p;set +o);"'set -- "${_a[@]}";unset _x _a';set +x;} 2>/dev/null
exec sudo -u "$1" env "_x=$_x" bash -c 'eval "$_x" 2> /dev/null;. "$0"
' "$0";}
Aqui está a definição do SWITCH_TO_USER função . Veremos abaixo que SWITCH_TO_USER acabará por ser um alias envolvido nessa função.
Essa função faz a maior parte da reexecução do script. No final, vemos que ele é reexecutado (no mesmo processo por causa de exec) bashcom a _xvariável em seu ambiente (usamos envaqui porque sudogeralmente higieniza seu ambiente e não permite a passagem de envios arbitrários). Isso bashavalia o conteúdo dessa $_xvariável como código bash e origina o próprio script.
_x é definido anteriormente como:
_x="$(declare;alias;shopt -p;set +o);"'set -- "${_a[@]}";unset _x _a'
Todo o declare, alias, shopt -p set +ode saída tornar-se um dump do estado interno do shell. Ou seja, eles despejam a definição de todas as variáveis, funções, aliases e opções como código de shell pronto para ser avaliado. Além disso, adicionamos a configuração dos parâmetros posicionais ( $1, $2...) com base no valor do$_a matriz (veja abaixo), e alguns limpam para que a grande $_xvariável não permaneça no ambiente pelo restante do script.
Você notará que a primeira parte até set +xé agrupada em um grupo de comandos cujo stderr é redirecionado para /dev/null( {...} 2> /dev/null). Isso porque, se em algum momento do script set -x(ou set -o xtrace) for executado, não queremos que esse preâmbulo gere rastreamentos, pois queremos torná-lo o menos invasivo possível. Portanto, executamos um set +x(depois de ter certeza de despejar as xtraceconfigurações da opção (incluindo ) antecipadamente) em que os rastreamentos são enviados para / dev / null.
O eval "$_X"stderr também é redirecionado para / dev / null por razões semelhantes, mas também para evitar erros na gravação de tentativa de variáveis especiais somente leitura.
Vamos continuar com o script:
alias skip=":||:<<'SWITCH_TO_USER $_u'"
Esse é o nosso truque descrito acima. Na invocação inicial do script, ele será cancelado (veja abaixo).
alias SWITCH_TO_USER="{ eval '"'_a=("$@")'"';} 2>/dev/null;SWITCH_TO_USER"
Agora, o alias envolve o SWITCH_TO_USER. O principal motivo é poder passar os parâmetros posicionais ( $1, $2...) para o novo bashque interpretará o restante do script. Não foi possível fazê-lo na SWITCH_TO_USER função porque, dentro da função, "$@"estão os argumentos para as funções, não os dos scripts. O redirecionamento stderr para / dev / null é novamente para ocultar xtraces, e evalé para contornar um erro bash. Então chamamos a SWITCH_TO_USER função .
${_u+:} alias skip=:
Essa parte cancela o skipalias (substitui-o :pelo comando no-op), a menos que a $_uvariável esteja definida.
skip
Esse é o nosso skipapelido. Na primeira invocação, será apenas :(o não-op). Em subsequence re-invocações, será algo como: :||: << 'SWITCH_TO_USER root'.
echo test
a=foo
set a b
SWITCH_TO_USER root
Então, aqui, como exemplo, nesse ponto, reinvocamos o script como rootusuário, e o script restaurará o estado salvo e pularemos para esseSWITCH_TO_USER root linha e continuaremos.
O que isso significa é que ele deve ser escrito exatamente como stat, com SWITCH_TO_USER no início da linha e com exatamente um espaço entre argumentos.
A maior parte do estado, stdin, stdout e stderr será preservada, mas não os outros descritores de arquivo, porque sudonormalmente os fecha, a menos que explicitamente configurado para não. Então, por exemplo:
exec 3> some-file
SWITCH_TO_USER bob
echo test >&3
normalmente não funcionará.
Observe também que se você fizer:
SWITCH_TO_USER alice
SWITCH_TO_USER bob
SWITCH_TO_USER root
Isso só funciona se você tiver o direito de sudocomo alicee alicetem o direito de sudocomo bobe bobcomoroot .
Portanto, na prática, isso não é realmente útil. Usar em suvez de sudo(ou uma sudoconfiguração em que sudoautentica o usuário de destino em vez do chamador) pode fazer um pouco mais de sentido, mas isso ainda significa que você precisa saber as senhas de todos esses caras.