21

I'm working on a script that runs a command as sudo and echoes a line of text ONLY if my sudo privileges have timed out, so only if running a command with sudo would require my user (not root) to type its password again.

How do I verify that? Mind that $(id -u) even when running as sudo will return my current user id so that can't be check to match it with 0...

I need a method that would check this quietly.

0

3 Answers 3

29

Use the option -n to check whether you still have privileges; from man sudo:

-n, --non-interactive

Avoid prompting the user for input of any kind. If a password is required for the command to run, sudo will display an error message and exit.

For example,

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

Be aware that it is possible for the privileges to expire between checking with sudo -n true and actually using them. You may want to try directly with sudo -n command... and in case of failure display a message and possibly retry running sudo interactively.

Edit: See also ruakh's comment below.

4
  • Thank you I've tried something similar before but I just couldn't get it to work the way I wanted. Dec 15, 2016 at 18:59
  • 4
    Re: "Be aware that it is possible for the privileges to expire between checking with sudo -n true and actually using them": The documentation is a bit vague on this point, but I think that running a sudo command, even just sudo -n true, will re-set the timeout clock. Either way, -v is explicitly documented as doing so, and sudo -n -v is probably more appropriate than sudo -n true for this purpose anyway.
    – ruakh
    Dec 16, 2016 at 6:45
  • While this will be indeed silent, it will log error to system journal if there are no privileges: hostname sudo[8870]: username : a password is required ; TTY=pts/0 ; PWD=/home/username ; USER=root ; COMMAND=/usr/bin/true. If you use it in bash prompt, for example, it will result in lots of error messages.
    – Rogach
    Dec 22, 2016 at 8:05
  • And if there are privileges, there will be debug logging from pam_unix and sudo command execution.
    – Rogach
    Dec 22, 2016 at 8:12
10

Run:

sudo -nv

If your sudo privileges have timed out, this will exit with an exit code of 1 and output:

sudo: a password is required

If you have valid cached credentials, this command will succeed and output nothing.

So, to put it all together, here's a scriptlet that will silently check if you have valid cached credentials:

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

As other answers/comments mentioned, the -v option ("validate") to sudo silently renews cached credentials if there are any or else prompts for authentication in order to generate cached credentials, and the -n option ("non-interactive") prevents sudo from generating any interactive prompts, such as the authentication prompt.

1
  • This is a good solution... I tried it before but AlexP's answer does exactly what I needed... I think I misunderstood the -n parameter before Dec 15, 2016 at 19:21
0

sudo -nv works fine, but pollutes system logs with sudo errors and pam authentication info. I needed to check sudo privileges for my bash prompt, so it was executed quite often and my logs consisted almost only of this noise.

It is possible to parse sudo timestamp file directly - I wrote a small C util for it:

/* 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;
}

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .