pam_timestamp.c revision 45405cce0657d01714b3d014a0facf3bdce45736
/*
* This file and its contents are supplied under the terms of the
* Common Development and Distribution License ("CDDL"), version 1.0.
* You may only use this file in accordance with the terms of version
* 1.0 of the CDDL.
*
* A full copy of the text of the CDDL should have accompanied this
* source. A copy of the CDDL is also available via the Internet at
* http://www.illumos.org/license/CDDL.
*/
/*
* Copyright 2014 Nexenta Systems, Inc.
*/
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <fcntl.h>
#include <security/pam_appl.h>
#include <security/pam_modules.h>
#include <security/pam_impl.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <syslog.h>
#include <unistd.h>
#include <libgen.h>
#include <errno.h>
#define TIMESTAMP_DIR "/var/run/tty_timestamps"
#define TIMESTAMP_TIMEOUT 5 /* default timeout */
#define ROOT_UID 0 /* root uid */
#define ROOT_GID 0 /* root gid */
struct user_info {
dev_t dev; /* ID of device tty resides on */
dev_t rdev; /* tty device ID */
ino_t ino; /* tty inode number */
uid_t uid; /* user's uid */
pid_t ppid; /* parent pid */
pid_t sid; /* session ID associated with tty/ppid */
timestruc_t ts; /* time of tty last status change */
};
int debug = 0;
int
validate_basic(
pam_handle_t *pamh,
char *user_tty,
char *timestampfile)
{
char *user;
char *auser;
char *ttyn;
/* get user, auser and users's tty */
(void) pam_get_item(pamh, PAM_USER, (void **)&user);
(void) pam_get_item(pamh, PAM_AUSER, (void **)&auser);
(void) pam_get_item(pamh, PAM_TTY, (void **)&ttyn);
if (user == NULL || *user == '\0') {
syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
"PAM_USER NULL or empty");
return (PAM_IGNORE);
}
if (auser == NULL || *auser == '\0') {
syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
"PAM_AUSER NULL or empty");
return (PAM_IGNORE);
}
if (ttyn == NULL || *ttyn == '\0') {
syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
"PAM_TTY NULL or empty");
return (PAM_IGNORE);
}
if (debug)
syslog(LOG_AUTH | LOG_DEBUG, "pam_timestamp: "
"user = %s, auser = %s, tty = %s", user, auser, ttyn);
(void) strlcpy(user_tty, ttyn, MAXPATHLEN);
if (strchr(ttyn, '/') == NULL || strncmp(ttyn, "/dev/", 5) == 0) {
ttyn = strrchr(ttyn, '/') + 1;
} else {
syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
"invalid tty: %s", ttyn);
return (PAM_IGNORE);
}
/* format timestamp file name */
(void) snprintf(timestampfile, MAXPATHLEN, "%s/%s/%s:%s", TIMESTAMP_DIR,
auser, ttyn, user);
return (PAM_SUCCESS);
}
int
validate_dir(const char *dir)
{
struct stat sb;
/*
* check that the directory exist and has
* right owner and permissions.
*/
if (lstat(dir, &sb) < 0) {
syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
"directory %s does not exist", dir);
return (PAM_IGNORE);
}
if (!S_ISDIR(sb.st_mode)) {
syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
"%s is not a directory", dir);
return (PAM_IGNORE);
}
if (S_ISLNK(sb.st_mode)) {
syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
"%s is a symbolic link", dir);
return (PAM_IGNORE);
}
if (sb.st_uid != 0 || sb.st_gid != 0) {
syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
"%s is not owned by root", dir);
return (PAM_IGNORE);
}
if (sb.st_mode & (S_IWGRP | S_IWOTH | S_IROTH)) {
syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
"%s has wrong permissions", dir);
return (PAM_IGNORE);
}
return (PAM_SUCCESS);
}
int
create_dir(char *dir)
{
/*
* create directory if it doesn't exist and attempt to set
* the owner to root.
*/
if (mkdir(dir, S_IRWXU) < 0) {
if (errno != EEXIST) {
syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
"can't create directory %s", dir);
return (PAM_IGNORE);
}
} else if (lchown(dir, ROOT_UID, ROOT_GID) < 0) {
syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
"can't set permissions on directory %s", dir);
return (PAM_IGNORE);
}
return (PAM_SUCCESS);
}
/*
* pam_sm_authenticate
*
* Read authentication from user, using cached successful authentication
* attempts.
*
* returns PAM_SUCCESS on success, otherwise always returns PAM_IGNORE:
* while this module has "sufficient" control value, in case of any failure
* user will be authenticated with the pam_unix_auth module.
* options -
* debug
* timeout= timeout in min, default is 5
*/
/*ARGSUSED*/
int
pam_sm_authenticate(
pam_handle_t *pamh,
int flags,
int argc,
const char **argv)
{
struct user_info info;
struct stat sb, tty;
time_t timeout = 0;
long tmp = 0;
int result = PAM_IGNORE;
int i;
int fd = -1;
char *p;
char user_tty[MAXPATHLEN];
char timestampdir[MAXPATHLEN];
char timestampfile[MAXPATHLEN];
char *sudir;
timeout = TIMESTAMP_TIMEOUT;
/* check options passed to this module */
for (i = 0; i < argc; i++) {
if (strcmp(argv[i], "debug") == 0) {
debug = 1;
} else if (strncmp(argv[i], "timeout=", 8) == 0) {
tmp = strtol(argv[i] + 8, &p, 0);
if ((p != NULL) && (*p == '\0') && tmp > 0) {
timeout = tmp;
}
}
}
if (validate_basic(pamh, user_tty, timestampfile) != PAM_SUCCESS)
return (result);
sudir = TIMESTAMP_DIR;
if (validate_dir(sudir) != PAM_SUCCESS)
return (result);
(void) strlcpy(timestampdir, timestampfile, MAXPATHLEN);
if (validate_dir(dirname(timestampdir)) != PAM_SUCCESS)
return (result);
/*
* check that timestamp file is exist and has right owner
* and permissions.
*/
if (lstat(timestampfile, &sb) == 0 && sb.st_size != 0) {
if (!S_ISREG(sb.st_mode)) {
(void) unlink(timestampfile);
syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
"timestamp file %s is not a regular file",
timestampfile);
return (result);
}
if (sb.st_uid != 0 || sb.st_gid != 0) {
(void) unlink(timestampfile);
syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
"timestamp file %s is not owned by root",
timestampfile);
return (result);
}
if (sb.st_nlink != 1 || S_ISLNK(sb.st_mode)) {
(void) unlink(timestampfile);
syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
"timestamp file %s is a symbolic link",
timestampfile);
return (result);
}
if (sb.st_mode & (S_IRWXG | S_IRWXO)) {
(void) unlink(timestampfile);
syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
"timestamp file %s has wrong permissions",
timestampfile);
return (result);
}
} else {
if (debug)
syslog(LOG_AUTH | LOG_DEBUG, "pam_timestamp: "
"timestamp file %s does not exist: %m",
timestampfile);
return (result);
}
if (stat(user_tty, &tty) < 0) {
syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
"can't stat tty: %m");
return (result);
}
if ((fd = open(timestampfile, O_RDONLY)) < 0) {
syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
"can't open timestamp file %s for reading: %m",
timestampfile);
return (result);
}
if (read(fd, &info, sizeof (info)) != sizeof (info)) {
(void) close(fd);
(void) unlink(timestampfile);
syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
"timestamp file '%s' is corrupt: %m", timestampfile);
return (result);
}
if (info.dev != tty.st_dev || info.ino != tty.st_ino ||
info.rdev != tty.st_rdev || info.sid != getsid(getpid()) ||
info.uid != getuid() || info.ts.tv_sec != tty.st_ctim.tv_sec ||
info.ts.tv_nsec != tty.st_ctim.tv_nsec) {
(void) close(fd);
(void) unlink(timestampfile);
syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
"the content of the timestamp file '%s' is not valid",
timestampfile);
return (result);
}
if (time((time_t *)0) - sb.st_mtime > 60 * timeout) {
(void) unlink(timestampfile);
syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
"timestamp file '%s' has expired, disallowing access",
timestampfile);
return (result);
} else {
if (debug)
syslog(LOG_AUTH | LOG_DEBUG, "pam_timestamp: "
"timestamp file %s is not expired, "
"allowing access ", timestampfile);
result = PAM_SUCCESS;
}
return (result);
}
/*
* pam_sm_setcred
*
* Creates timestamp directory and writes
* timestamp file if it doesn't exist.
*
* returns PAM_SUCCESS on success, otherwise PAM_IGNORE
*/
/*ARGSUSED*/
int
pam_sm_setcred(
pam_handle_t *pamh,
int flags,
int argc,
const char **argv)
{
struct stat sb;
struct stat tty;
struct user_info info;
int result = PAM_IGNORE;
int fd = -1;
char user_tty[MAXPATHLEN];
char timestampdir[MAXPATHLEN];
char timestampfile[MAXPATHLEN];
/* validate flags */
if (flags && !(flags & PAM_ESTABLISH_CRED) &&
!(flags & PAM_REINITIALIZE_CRED) &&
!(flags & PAM_REFRESH_CRED) &&
!(flags & PAM_DELETE_CRED) &&
!(flags & PAM_SILENT)) {
syslog(LOG_ERR, "pam_timestamp: illegal flag %d", flags);
return (result);
}
if (validate_basic(pamh, user_tty, timestampfile) != PAM_SUCCESS)
return (result);
/*
* user doesn't need to authenticate for PAM_DELETE_CRED
*/
if (flags & PAM_DELETE_CRED) {
(void) unlink(timestampfile);
return (result);
}
/* if the timestamp file exist, there is nothing to do */
if (lstat(timestampfile, &sb) == 0) {
if (debug)
syslog(LOG_AUTH | LOG_DEBUG, "pam_timestamp: "
"timestamp file %s is not expired", timestampfile);
return (result);
}
if (create_dir(TIMESTAMP_DIR) != PAM_SUCCESS)
return (result);
(void) strlcpy(timestampdir, timestampfile, MAXPATHLEN);
if (create_dir(dirname(timestampdir)) != PAM_SUCCESS)
return (result);
if (stat(user_tty, &tty) < 0) {
syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
"can't stat tty: %m");
return (result);
}
info.dev = tty.st_dev;
info.ino = tty.st_ino;
info.rdev = tty.st_rdev;
info.sid = getsid(getpid());
info.uid = getuid();
info.ts = tty.st_ctim;
if ((fd = open(timestampfile, O_WRONLY|O_CREAT, S_IRUSR|S_IWUSR)) < 0) {
syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
"can't open timestamp file %s for writing: %m",
timestampfile);
return (result);
} else if (fchown(fd, ROOT_UID, ROOT_GID) != 0) {
syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
"can't set permissions on timestamp file %s: %m",
timestampfile);
(void) close(fd);
return (result);
}
if (write(fd, &info, sizeof (info)) != sizeof (info)) {
(void) close(fd);
syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
"can't write timestamp file %s: %m", timestampfile);
return (result);
}
(void) close(fd);
return (PAM_SUCCESS);
}