diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 14:22:51 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 14:22:51 +0000 |
commit | 9ada0093e92388590c7368600ca4e9e3e376f0d0 (patch) | |
tree | a56fe41110023676d7082028cbaa47ca4b6e6164 /modules/pam_timestamp/pam_timestamp.c | |
parent | Initial commit. (diff) | |
download | pam-9ada0093e92388590c7368600ca4e9e3e376f0d0.tar.xz pam-9ada0093e92388590c7368600ca4e9e3e376f0d0.zip |
Adding upstream version 1.5.2.upstream/1.5.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'modules/pam_timestamp/pam_timestamp.c')
-rw-r--r-- | modules/pam_timestamp/pam_timestamp.c | 873 |
1 files changed, 873 insertions, 0 deletions
diff --git a/modules/pam_timestamp/pam_timestamp.c b/modules/pam_timestamp/pam_timestamp.c new file mode 100644 index 0000000..01dd138 --- /dev/null +++ b/modules/pam_timestamp/pam_timestamp.c @@ -0,0 +1,873 @@ +/****************************************************************************** + * A module for Linux-PAM that will cache authentication results, inspired by + * (and implemented with an eye toward being mixable with) sudo. + * + * Copyright (c) 2002 Red Hat, Inc. + * Written by Nalin Dahyabhai <nalin@redhat.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, and the entire permission notice in its entirety, + * including the disclaimer of warranties. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * ALTERNATIVELY, this product may be distributed under the terms of + * the GNU Public License, in which case the provisions of the GPL are + * required INSTEAD OF the above restrictions. (This clause is + * necessary due to a potential bad interaction between the GPL and + * the restrictions contained in a BSD-style copyright.) + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "config.h" + +#include <sys/stat.h> +#include <sys/types.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <pwd.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <sys/time.h> +#include <unistd.h> +#include <utmp.h> +#include <syslog.h> +#include <paths.h> +#ifdef WITH_OPENSSL +#include "hmac_openssl_wrapper.h" +#else +#include "hmacsha1.h" +#endif /* WITH_OPENSSL */ + +#include <security/pam_modules.h> +#include <security/_pam_macros.h> +#include <security/pam_ext.h> +#include <security/pam_modutil.h> +#include "pam_inline.h" + +/* The default timeout we use is 5 minutes, which matches the sudo default + * for the timestamp_timeout parameter. */ +#define DEFAULT_TIMESTAMP_TIMEOUT (5 * 60) +#define MODULE "pam_timestamp" +#define TIMESTAMPDIR _PATH_VARRUN MODULE +#define TIMESTAMPKEY TIMESTAMPDIR "/_pam_timestamp_key" + +/* Various buffers we use need to be at least as large as either PATH_MAX or + * LINE_MAX, so choose the larger of the two. */ +#if (LINE_MAX > PATH_MAX) +#define BUFLEN LINE_MAX +#else +#define BUFLEN PATH_MAX +#endif + +#define ROOT_USER 0 +#define ROOT_GROUP 0 + +/* Return PAM_SUCCESS if the given directory looks "safe". */ +static int +check_dir_perms(pam_handle_t *pamh, const char *tdir) +{ + char scratch[BUFLEN]; + struct stat st; + int i; + /* Check that the directory is "safe". */ + if ((tdir == NULL) || (strlen(tdir) == 0)) { + return PAM_AUTH_ERR; + } + /* Iterate over the path, checking intermediate directories. */ + memset(scratch, 0, sizeof(scratch)); + for (i = 0; (tdir[i] != '\0') && (i < (int)sizeof(scratch)); i++) { + scratch[i] = tdir[i]; + if ((scratch[i] == '/') || (tdir[i + 1] == '\0')) { + /* We now have the name of a directory in the path, so + * we need to check it. */ + if ((lstat(scratch, &st) == -1) && (errno != ENOENT)) { + pam_syslog(pamh, LOG_ERR, + "unable to read `%s': %m", + scratch); + return PAM_AUTH_ERR; + } + if (!S_ISDIR(st.st_mode)) { + pam_syslog(pamh, LOG_ERR, + "`%s' is not a directory", + scratch); + return PAM_AUTH_ERR; + } + if (S_ISLNK(st.st_mode)) { + pam_syslog(pamh, LOG_ERR, + "`%s' is a symbolic link", + scratch); + return PAM_AUTH_ERR; + } + if (st.st_uid != 0) { + pam_syslog(pamh, LOG_ERR, + "`%s' owner UID != 0", + scratch); + return PAM_AUTH_ERR; + } + if (st.st_gid != 0) { + pam_syslog(pamh, LOG_ERR, + "`%s' owner GID != 0", + scratch); + return PAM_AUTH_ERR; + } + if ((st.st_mode & (S_IWGRP | S_IWOTH)) != 0) { + pam_syslog(pamh, LOG_ERR, + "`%s' permissions are lax", + scratch); + return PAM_AUTH_ERR; + } + } + } + return PAM_SUCCESS; +} + +/* Validate a tty pathname as actually belonging to a tty, and return its base + * name if it's valid. */ +static const char * +check_tty(const char *tty) +{ + /* Check that we're not being set up to take a fall. */ + if ((tty == NULL) || (strlen(tty) == 0)) { + return NULL; + } + /* Pull out the meaningful part of the tty's name. */ + if (strchr(tty, '/') != NULL) { + if (pam_str_skip_prefix(tty, "/dev/") == NULL) { + /* Make sure the device node is actually in /dev/, + * noted by Michal Zalewski. */ + return NULL; + } + tty = strrchr(tty, '/') + 1; + } + /* Make sure the tty wasn't actually a directory (no basename). */ + if (!strlen(tty) || !strcmp(tty, ".") || !strcmp(tty, "..")) { + return NULL; + } + return tty; +} + +/* Determine the right path name for a given user's timestamp. */ +static int +format_timestamp_name(char *path, size_t len, + const char *timestamp_dir, + const char *tty, + const char *ruser, + const char *user) +{ + if (strcmp(ruser, user) == 0) { + return snprintf(path, len, "%s/%s/%s", timestamp_dir, + ruser, tty); + } else { + return snprintf(path, len, "%s/%s/%s:%s", timestamp_dir, + ruser, tty, user); + } +} + +/* Check if a given timestamp date, when compared to a current time, fits + * within the given interval. */ +static int +timestamp_good(time_t then, time_t now, time_t interval) +{ + if (((now >= then) && ((now - then) < interval)) || + ((now < then) && ((then - now) < (2 * interval)))) { + return PAM_SUCCESS; + } + return PAM_AUTH_ERR; +} + +static int +check_login_time(const char *ruser, time_t timestamp) +{ + struct utmp utbuf, *ut; + time_t oldest_login = 0; + + setutent(); + while( +#ifdef HAVE_GETUTENT_R + !getutent_r(&utbuf, &ut) +#else + (ut = getutent()) != NULL +#endif + ) { + if (ut->ut_type != USER_PROCESS) { + continue; + } + if (strncmp(ruser, ut->ut_user, sizeof(ut->ut_user)) != 0) { + continue; + } + if (oldest_login == 0 || oldest_login > ut->ut_tv.tv_sec) { + oldest_login = ut->ut_tv.tv_sec; + } + } + endutent(); + if(oldest_login == 0 || timestamp < oldest_login) { + return PAM_AUTH_ERR; + } + return PAM_SUCCESS; +} + +#ifndef PAM_TIMESTAMP_MAIN +static int +get_ruser(pam_handle_t *pamh, char *ruserbuf, size_t ruserbuflen) +{ + const void *ruser; + struct passwd *pwd; + + if (ruserbuf == NULL || ruserbuflen < 1) + return -2; + /* Get the name of the source user. */ + if (pam_get_item(pamh, PAM_RUSER, &ruser) != PAM_SUCCESS) { + ruser = NULL; + } + if ((ruser == NULL) || (strlen(ruser) == 0)) { + /* Barring that, use the current RUID. */ + pwd = pam_modutil_getpwuid(pamh, getuid()); + if (pwd != NULL) { + ruser = pwd->pw_name; + } + } else { + /* + * This ruser is used by format_timestamp_name as a component + * of constructed timestamp pathname, so ".", "..", and '/' + * are disallowed to avoid potential path traversal issues. + */ + if (!strcmp(ruser, ".") || + !strcmp(ruser, "..") || + strchr(ruser, '/')) { + ruser = NULL; + } + } + if (ruser == NULL || strlen(ruser) >= ruserbuflen) { + *ruserbuf = '\0'; + return -1; + } + strcpy(ruserbuf, ruser); + return 0; +} + +/* Get the path to the timestamp to use. */ +static int +get_timestamp_name(pam_handle_t *pamh, int argc, const char **argv, + char *path, size_t len) +{ + const char *user, *tty; + const void *void_tty; + const char *tdir = TIMESTAMPDIR; + char ruser[BUFLEN]; + int i, debug = 0; + + /* Parse arguments. */ + for (i = 0; i < argc; i++) { + if (strcmp(argv[i], "debug") == 0) { + debug = 1; + } + } + for (i = 0; i < argc; i++) { + const char *str; + + if ((str = pam_str_skip_prefix(argv[i], "timestampdir=")) != NULL) { + tdir = str; + if (debug) { + pam_syslog(pamh, LOG_DEBUG, + "storing timestamps in `%s'", + tdir); + } + } + } + i = check_dir_perms(pamh, tdir); + if (i != PAM_SUCCESS) { + return i; + } + /* Get the name of the target user. */ + if (pam_get_user(pamh, &user, NULL) != PAM_SUCCESS || user[0] == '\0') { + return PAM_AUTH_ERR; + } + if (debug) { + pam_syslog(pamh, LOG_DEBUG, "becoming user `%s'", user); + } + /* Get the name of the source user. */ + if (get_ruser(pamh, ruser, sizeof(ruser)) || strlen(ruser) == 0) { + return PAM_AUTH_ERR; + } + if (debug) { + pam_syslog(pamh, LOG_DEBUG, "currently user `%s'", ruser); + } + /* Get the name of the terminal. */ + if (pam_get_item(pamh, PAM_TTY, &void_tty) != PAM_SUCCESS) { + tty = NULL; + } else { + tty = void_tty; + } + if ((tty == NULL) || (strlen(tty) == 0)) { + tty = ttyname(STDIN_FILENO); + if ((tty == NULL) || (strlen(tty) == 0)) { + tty = ttyname(STDOUT_FILENO); + } + if ((tty == NULL) || (strlen(tty) == 0)) { + tty = ttyname(STDERR_FILENO); + } + if ((tty == NULL) || (strlen(tty) == 0)) { + /* Match sudo's behavior for this case. */ + tty = "unknown"; + } + } + if (debug) { + pam_syslog(pamh, LOG_DEBUG, "tty is `%s'", tty); + } + /* Snip off all but the last part of the tty name. */ + tty = check_tty(tty); + if (tty == NULL) { + return PAM_AUTH_ERR; + } + /* Generate the name of the file used to cache auth results. These + * paths should jive with sudo's per-tty naming scheme. */ + if (format_timestamp_name(path, len, tdir, tty, ruser, user) >= (int)len) { + return PAM_AUTH_ERR; + } + if (debug) { + pam_syslog(pamh, LOG_DEBUG, "using timestamp file `%s'", path); + } + return PAM_SUCCESS; +} + +/* Tell the user that access has been granted. */ +static void +verbose_success(pam_handle_t *pamh, long diff) +{ + pam_info(pamh, _("Access has been granted" + " (last access was %ld seconds ago)."), diff); +} + +int +pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv) +{ + struct stat st; + time_t interval = DEFAULT_TIMESTAMP_TIMEOUT; + int i, fd, debug = 0, verbose = 0; + char path[BUFLEN], *p, *message, *message_end; + long tmp; + const void *void_service; + const char *service; + time_t now, then; + + /* Parse arguments. */ + for (i = 0; i < argc; i++) { + if (strcmp(argv[i], "debug") == 0) { + debug = 1; + } + } + for (i = 0; i < argc; i++) { + const char *str; + + if ((str = pam_str_skip_prefix(argv[i], "timestamp_timeout=")) != NULL) { + tmp = strtol(str, &p, 0); + if ((p != NULL) && (*p == '\0')) { + interval = tmp; + if (debug) { + pam_syslog(pamh, LOG_DEBUG, + "setting timeout to %ld" + " seconds", (long)interval); + } + } + } else + if (strcmp(argv[i], "verbose") == 0) { + verbose = 1; + if (debug) { + pam_syslog(pamh, LOG_DEBUG, + "becoming more verbose"); + } + } + } + + if (flags & PAM_SILENT) { + verbose = 0; + } + + /* Get the name of the timestamp file. */ + if (get_timestamp_name(pamh, argc, argv, + path, sizeof(path)) != PAM_SUCCESS) { + return PAM_AUTH_ERR; + } + + /* Get the name of the service. */ + if (pam_get_item(pamh, PAM_SERVICE, &void_service) != PAM_SUCCESS) { + service = NULL; + } else { + service = void_service; + } + if ((service == NULL) || (strlen(service) == 0)) { + service = "(unknown)"; + } + + /* Open the timestamp file. */ + fd = open(path, O_RDONLY | O_NOFOLLOW); + if (fd == -1) { + if (debug) { + pam_syslog(pamh, LOG_DEBUG, + "cannot open timestamp `%s': %m", + path); + } + return PAM_AUTH_ERR; + } + + if (fstat(fd, &st) == 0) { + int count; + void *mac; + size_t maclen; + char ruser[BUFLEN]; + + /* Check that the file is owned by the superuser. */ + if ((st.st_uid != 0) || (st.st_gid != 0)) { + pam_syslog(pamh, LOG_ERR, "timestamp file `%s' is " + "not owned by root", path); + close(fd); + return PAM_AUTH_ERR; + } + + /* Check that the file is a normal file. */ + if (!(S_ISREG(st.st_mode))) { + pam_syslog(pamh, LOG_ERR, "timestamp file `%s' is " + "not a regular file", path); + close(fd); + return PAM_AUTH_ERR; + } + +#ifdef WITH_OPENSSL + if (hmac_size(pamh, debug, &maclen)) { + return PAM_AUTH_ERR; + } +#else + maclen = hmac_sha1_size(); +#endif /* WITH_OPENSSL */ + /* Check that the file is the expected size. */ + if (st.st_size == 0) { + /* Invalid, but may have been created by sudo. */ + close(fd); + return PAM_AUTH_ERR; + } + if (st.st_size != + (off_t)(strlen(path) + 1 + sizeof(then) + maclen)) { + pam_syslog(pamh, LOG_NOTICE, "timestamp file `%s' " + "appears to be corrupted", path); + close(fd); + return PAM_AUTH_ERR; + } + + /* Read the file contents. */ + message = malloc(st.st_size); + count = 0; + if (!message) { + close(fd); + return PAM_BUF_ERR; + } + while (count < st.st_size) { + i = read(fd, message + count, st.st_size - count); + if ((i == 0) || (i == -1)) { + break; + } + count += i; + } + if (count < st.st_size) { + pam_syslog(pamh, LOG_NOTICE, "error reading timestamp " + "file `%s': %m", path); + close(fd); + free(message); + return PAM_AUTH_ERR; + } + message_end = message + strlen(path) + 1 + sizeof(then); + + /* Regenerate the MAC. */ +#ifdef WITH_OPENSSL + if (hmac_generate(pamh, debug, &mac, &maclen, TIMESTAMPKEY, + ROOT_USER, ROOT_GROUP, message, message_end - message)) { + close(fd); + free(message); + return PAM_AUTH_ERR; + } +#else + hmac_sha1_generate_file(pamh, &mac, &maclen, TIMESTAMPKEY, + ROOT_USER, ROOT_GROUP, message, message_end - message); +#endif /* WITH_OPENSSL */ + if ((mac == NULL) || + (memcmp(path, message, strlen(path)) != 0) || + (memcmp(mac, message_end, maclen) != 0)) { + pam_syslog(pamh, LOG_NOTICE, "timestamp file `%s' is " + "corrupted", path); + close(fd); + free(mac); + free(message); + return PAM_AUTH_ERR; + } + free(mac); + memmove(&then, message + strlen(path) + 1, sizeof(then)); + free(message); + + /* Check oldest login against timestamp */ + if (get_ruser(pamh, ruser, sizeof(ruser))) + { + close(fd); + return PAM_AUTH_ERR; + } + if (check_login_time(ruser, then) != PAM_SUCCESS) + { + pam_syslog(pamh, LOG_NOTICE, "timestamp file `%s' is " + "older than oldest login, disallowing " + "access to %s for user %s", + path, service, ruser); + close(fd); + return PAM_AUTH_ERR; + } + + /* Compare the dates. */ + now = time(NULL); + if (timestamp_good(then, now, interval) == PAM_SUCCESS) { + close(fd); + pam_syslog(pamh, LOG_NOTICE, "timestamp file `%s' is " + "only %ld seconds old, allowing access to %s " + "for user %s", path, (long) (now - st.st_mtime), + service, ruser); + if (verbose) { + verbose_success(pamh, now - st.st_mtime); + } + return PAM_SUCCESS; + } else { + close(fd); + pam_syslog(pamh, LOG_NOTICE, "timestamp file `%s' has " + "unacceptable age (%ld seconds), disallowing " + "access to %s for user %s", + path, (long) (now - st.st_mtime), + service, ruser); + return PAM_AUTH_ERR; + } + } + close(fd); + + /* Fail by default. */ + return PAM_AUTH_ERR; +} + +int +pam_sm_setcred(pam_handle_t *pamh UNUSED, int flags UNUSED, int argc UNUSED, const char **argv UNUSED) +{ + return PAM_SUCCESS; +} + +int +pam_sm_open_session(pam_handle_t *pamh, int flags UNUSED, int argc, const char **argv) +{ + char path[BUFLEN], subdir[BUFLEN], *text, *p; + void *mac; + size_t maclen; + time_t now; + int fd, i, debug = 0; + + /* Parse arguments. */ + for (i = 0; i < argc; i++) { + if (strcmp(argv[i], "debug") == 0) { + debug = 1; + } + } + + /* Get the name of the timestamp file. */ + if (get_timestamp_name(pamh, argc, argv, + path, sizeof(path)) != PAM_SUCCESS) { + return PAM_SESSION_ERR; + } + + /* Create the directory for the timestamp file if it doesn't already + * exist. */ + for (i = 1; i < (int) sizeof(path) && path[i] != '\0'; i++) { + if (path[i] == '/') { + /* Attempt to create the directory. */ + memcpy(subdir, path, i); + subdir[i] = '\0'; + if (mkdir(subdir, 0700) == 0) { + /* Attempt to set the owner to the superuser. */ + if (lchown(subdir, 0, 0) != 0) { + if (debug) { + pam_syslog(pamh, LOG_DEBUG, + "error setting permissions on `%s': %m", + subdir); + } + return PAM_SESSION_ERR; + } + } else { + if (errno != EEXIST) { + if (debug) { + pam_syslog(pamh, LOG_DEBUG, + "error creating directory `%s': %m", + subdir); + } + return PAM_SESSION_ERR; + } + } + } + } + +#ifdef WITH_OPENSSL + if (hmac_size(pamh, debug, &maclen)) { + return PAM_SESSION_ERR; + } +#else + maclen = hmac_sha1_size(); +#endif /* WITH_OPENSSL */ + + /* Generate the message. */ + text = malloc(strlen(path) + 1 + sizeof(now) + maclen); + if (text == NULL) { + pam_syslog(pamh, LOG_CRIT, "unable to allocate memory: %m"); + return PAM_SESSION_ERR; + } + p = text; + + strcpy(text, path); + p += strlen(path) + 1; + + now = time(NULL); + memmove(p, &now, sizeof(now)); + p += sizeof(now); + + /* Generate the MAC and append it to the plaintext. */ +#ifdef WITH_OPENSSL + if (hmac_generate(pamh, debug, &mac, &maclen, TIMESTAMPKEY, + ROOT_USER, ROOT_GROUP, text, p - text)) { + free(text); + return PAM_SESSION_ERR; + } +#else + hmac_sha1_generate_file(pamh, &mac, &maclen, TIMESTAMPKEY, + ROOT_USER, ROOT_GROUP, text, p - text); + if (mac == NULL) { + pam_syslog(pamh, LOG_ERR, "failure generating MAC: %m"); + free(text); + return PAM_SESSION_ERR; + } +#endif /* WITH_OPENSSL */ + memmove(p, mac, maclen); + p += maclen; + free(mac); + + /* Open the file. */ + fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); + if (fd == -1) { + pam_syslog(pamh, LOG_ERR, "unable to open `%s': %m", path); + free(text); + return PAM_SESSION_ERR; + } + + /* Attempt to set the owner to the superuser. */ + if (fchown(fd, 0, 0) != 0) { + if (debug) { + pam_syslog(pamh, LOG_DEBUG, + "error setting ownership of `%s': %m", + path); + } + close(fd); + free(text); + return PAM_SESSION_ERR; + } + + + /* Write the timestamp to the file. */ + if (write(fd, text, p - text) != p - text) { + pam_syslog(pamh, LOG_ERR, "unable to write to `%s': %m", path); + close(fd); + free(text); + return PAM_SESSION_ERR; + } + + /* Close the file and return successfully. */ + close(fd); + free(text); + pam_syslog(pamh, LOG_DEBUG, "updated timestamp file `%s'", path); + return PAM_SUCCESS; +} + +int +pam_sm_close_session(pam_handle_t *pamh UNUSED, int flags UNUSED, int argc UNUSED, const char **argv UNUSED) +{ + return PAM_SUCCESS; +} + +#else /* PAM_TIMESTAMP_MAIN */ + +#define USAGE "Usage: %s [[-k] | [-d]] [target user]\n" +#define CHECK_INTERVAL 7 + +int +main(int argc, char **argv) +{ + int i, retval = 0, dflag = 0, kflag = 0; + const char *target_user = NULL, *user = NULL, *tty = NULL; + struct passwd *pwd; + struct timeval tv; + fd_set write_fds; + char path[BUFLEN]; + struct stat st; + + /* Check that there's nothing funny going on with stdio. */ + if ((fstat(STDIN_FILENO, &st) == -1) || + (fstat(STDOUT_FILENO, &st) == -1) || + (fstat(STDERR_FILENO, &st) == -1)) { + /* Appropriate the "no controlling tty" error code. */ + return 3; + } + + /* Parse arguments. */ + while ((i = getopt(argc, argv, "dk")) != -1) { + switch (i) { + case 'd': + dflag++; + break; + case 'k': + kflag++; + break; + default: + fprintf(stderr, USAGE, argv[0]); + return 1; + break; + } + } + + /* Bail if both -k and -d are given together. */ + if ((kflag + dflag) > 1) { + fprintf(stderr, USAGE, argv[0]); + return 1; + } + + /* Check that we're setuid. */ + if (geteuid() != 0) { + fprintf(stderr, "%s must be setuid root\n", + argv[0]); + retval = 2; + } + + /* Check that we have a controlling tty. */ + tty = ttyname(STDIN_FILENO); + if ((tty == NULL) || (strlen(tty) == 0)) { + tty = ttyname(STDOUT_FILENO); + } + if ((tty == NULL) || (strlen(tty) == 0)) { + tty = ttyname(STDERR_FILENO); + } + if ((tty == NULL) || (strlen(tty) == 0)) { + tty = "unknown"; + } + + /* Get the name of the invoking (requesting) user. */ + pwd = getpwuid(getuid()); + if (pwd == NULL) { + retval = 4; + } + + /* Get the name of the target user. */ + user = strdup(pwd->pw_name); + if (user == NULL) { + retval = 4; + } else { + target_user = (optind < argc) ? argv[optind] : user; + if ((strchr(target_user, '.') != NULL) || + (strchr(target_user, '/') != NULL) || + (strchr(target_user, '%') != NULL)) { + fprintf(stderr, "unknown user: %s\n", + target_user); + retval = 4; + } + } + + /* Sanity check the tty to make sure we should be checking + * for timestamps which pertain to it. */ + if (retval == 0) { + tty = check_tty(tty); + if (tty == NULL) { + fprintf(stderr, "invalid tty\n"); + retval = 6; + } + } + + do { + /* Sanity check the timestamp directory itself. */ + if (retval == 0) { + if (check_dir_perms(NULL, TIMESTAMPDIR) != PAM_SUCCESS) { + retval = 5; + } + } + + if (retval == 0) { + /* Generate the name of the timestamp file. */ + format_timestamp_name(path, sizeof(path), TIMESTAMPDIR, + tty, user, target_user); + } + + if (retval == 0) { + if (kflag) { + /* Remove the timestamp. */ + if (lstat(path, &st) != -1) { + retval = unlink(path); + } + } else { + /* Check the timestamp. */ + if (lstat(path, &st) != -1) { + /* Check oldest login against timestamp */ + if (check_login_time(user, st.st_mtime) != PAM_SUCCESS) { + retval = 7; + } else if (timestamp_good(st.st_mtime, time(NULL), + DEFAULT_TIMESTAMP_TIMEOUT) != PAM_SUCCESS) { + retval = 7; + } + } else { + retval = 7; + } + } + } + + if (dflag > 0) { + struct timeval now; + /* Send the would-be-returned value to our parent. */ + signal(SIGPIPE, SIG_DFL); + fprintf(stdout, "%d\n", retval); + fflush(stdout); + /* Wait. */ + gettimeofday(&now, NULL); + tv.tv_sec = CHECK_INTERVAL; + /* round the sleep time to get woken up on a whole second */ + tv.tv_usec = 1000000 - now.tv_usec; + if (now.tv_usec < 500000) + tv.tv_sec--; + FD_ZERO(&write_fds); + FD_SET(STDOUT_FILENO, &write_fds); + select(STDOUT_FILENO + 1, + NULL, NULL, &write_fds, + &tv); + retval = 0; + } + } while (dflag > 0); + + return retval; +} + +#endif |