Como descubro se meu privilégio de sudoer expirou?

20

Estou trabalhando em um script que executa um comando como sudo e repete uma linha de texto SOMENTE se meus privilégios de sudo expirarem, portanto, somente se a execução de um comando com sudo exigir que meu usuário (não root) digite sua senha novamente.

Como verifico isso? Lembre-se de que $(id -u)mesmo quando estiver executando como sudo retornará meu ID de usuário atual, para que não seja possível verificar se ele corresponde a 0 ...

Eu preciso de um método que verifique isso silenciosamente.

TonyMorello
fonte

Respostas:

28

Use a opção -npara verificar se você ainda tem privilégios; de man sudo:

-n , --não-interativo

Evite solicitar ao usuário a inserção de qualquer tipo. Se uma senha for necessária para a execução do comando, o sudo exibirá uma mensagem de erro e sairá.

Por exemplo,

sudo -n true 2>/dev/null && echo Privileges active || echo Privileges inactive

Esteja ciente de que é possível que os privilégios expirem entre verificar sudo -n truee realmente usá-los. Convém tentar diretamente sudo -n command...exibir uma mensagem e, em caso de falha, tentar executar de forma sudointerativa.

Edit: Veja também o comentário de ruakh abaixo.

AlexP
fonte
Obrigado, eu tentei algo semelhante antes, mas simplesmente não consegui fazê-lo funcionar da maneira que eu queria.
TonyMorello
3
Re: "Esteja ciente de que é possível que os privilégios expirem entre verificar sudo -n truee realmente usá-los": A documentação é um pouco vaga sobre esse ponto, mas acho que executar um sudocomando, mesmo que apenas sudo -n true, redefinirá o tempo limite relógio. De qualquer maneira, -vestá explicitamente documentado como o faz e sudo -n -vprovavelmente é mais apropriado do que sudo -n truepara esse fim.
Ruakh
Enquanto esta será de fato em silêncio, ele irá registrar erro de diário do sistema se não há privilégios: hostname sudo[8870]: username : a password is required ; TTY=pts/0 ; PWD=/home/username ; USER=root ; COMMAND=/usr/bin/true. Se você usá-lo no prompt do bash, por exemplo, isso resultará em muitas mensagens de erro.
Rogach 22/12/16
E se houver privilégios, haverá o log de depuração da execução dos comandos pam_unix e sudo.
Rogach
8

Corre:

sudo -nv

Se os seus privilégios sudo expirarem, isso será encerrado com um código de saída 1 e será gerado:

sudo: a password is required

Se você tiver credenciais em cache válidas, esse comando será bem-sucedido e não produzirá nada.

Portanto, para juntar tudo, aqui está um scriptlet que verificará silenciosamente se você possui credenciais em cache válidas:

if sudo -nv 2>/dev/null; then
  echo "no sudo password required"
else
  echo "sudo password expired"
fi

Como outras respostas / comentários mencionados, a -vopção ("validar") para sudo renova silenciosamente as credenciais em cache se houver alguma solicitação de autenticação para gerar credenciais em cache, e a -nopção ("não interativa") impede que o sudo gere quaisquer avisos interativos, como o prompt de autenticação.

jayhendren
fonte
Esta é uma boa solução ... Eu tentei isso antes, mas a resposta de AlexP faz exatamente o que eu precisava ... Eu acho que eu entendi mal o parâmetro -n antes
TonyMorello
1

sudo -nvfunciona bem, mas polui os logs do sistema com erros sudo e informações de autenticação do pam. Eu precisava verificar os privilégios do sudo para o meu prompt do bash, por isso foi executado com bastante frequência e meus logs consistiam quase apenas desse ruído.

É possível analisar o arquivo sudo timestamp diretamente - escrevi um pequeno utilitário C para ele:

/* compile and set permissions: */
/* $ gcc checksudo.c -o checksudo -std=gnu99 -O2 */
/* $ chown root:root checksudo */
/* $ chmod +s checksudo */

#define USERNAME "replace-with-your-username"
#define TIMEOUT 5

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <time.h>

void timespec_diff(struct timespec *start, struct timespec *stop, struct timespec *result) {
    if ((stop->tv_nsec - start->tv_nsec) < 0) {
        result->tv_sec = stop->tv_sec - start->tv_sec - 1;
        result->tv_nsec = stop->tv_nsec - start->tv_nsec + 1000000000;
    } else {
        result->tv_sec = stop->tv_sec - start->tv_sec;
        result->tv_nsec = stop->tv_nsec - start->tv_nsec;
    }
    return;
}

int main(int argc, char** argv) {
  if (geteuid() != 0) {
    printf("uid is not 0 - checksudo must be owned by uid 0 and have the setuid bit set\n");
    return 2;
  }

  struct timespec current_time;
  if (clock_gettime(CLOCK_BOOTTIME, &current_time) != 0) {
    printf("Unable to get current time: %s\n", strerror(errno));
    return 2;
  }

  struct stat ttypath_stat;
  if (stat(ttyname(0), &ttypath_stat) != 0) {
    printf("Unable to stat current tty: %s\n", strerror(errno));
    return 2;
  }

  FILE* timestamp_fd = fopen("/var/run/sudo/ts/" USERNAME, "rb");
  if (timestamp_fd == NULL) {
    printf("Unable to open sudo timestamp file: %s\n", strerror(errno));
    return 2;
  }

  long offset = 0;
  int found = 0;

  while (1) {
    if (fseek(timestamp_fd, offset, SEEK_SET) != 0) {
      printf("Failed to seek timestamp file: %s\n", strerror(errno));
      return 2;
    }
    unsigned short timestamp_entry_header[4];
    if (feof(timestamp_fd)) {
      printf("matching timestamp not found\n");
      return 2;
    }
    if (fread(&timestamp_entry_header, sizeof(unsigned short), 4, timestamp_fd) < 4) {
      break;
    }
    if (ferror(timestamp_fd)) {
      printf("IO error when reading timestamp file\n");
      return 2;
    }

    // read tty device id
    if (timestamp_entry_header[2] == 2 && timestamp_entry_header[3] == 0) {
      if (fseek(timestamp_fd, offset + 32, SEEK_SET) != 0) {
        printf("Failed to seek timestamp file: %s\n", strerror(errno));
        return 2;
      }
      dev_t tty_dev_id;
      if (fread(&tty_dev_id, sizeof(dev_t), 1, timestamp_fd) < 1) {
        printf("EOF when reading tty device id\n");
        return 2;
      }
      if (tty_dev_id == ttypath_stat.st_rdev) {
        // read timestamp
        if (fseek(timestamp_fd, offset + 16, SEEK_SET) != 0) {
          printf("Failed to seek timestamp file: %s\n", strerror(errno));
          return 2;
        }
        struct timespec sudo_time;
        if (fread(&sudo_time, sizeof(struct timespec), 1, timestamp_fd) < 1) {
          printf("EOF when reading timestamp\n");
          return 2;
        }

        struct timespec time_since_sudo;
        timespec_diff(&sudo_time, &current_time, &time_since_sudo);
        found = time_since_sudo.tv_sec < TIMEOUT * 60;
        break;
      }
    }

    offset += timestamp_entry_header[1];
  }

  fclose(timestamp_fd);

  return !found;
}
Rogach
fonte