1 /*
   2  * This file and its contents are supplied under the terms of the
   3  * Common Development and Distribution License ("CDDL"), version 1.0.
   4  * You may only use this file in accordance with the terms of version
   5  * 1.0 of the CDDL.
   6  *
   7  * A full copy of the text of the CDDL should have accompanied this
   8  * source.  A copy of the CDDL is also available via the Internet at
   9  * http://www.illumos.org/license/CDDL.
  10  */
  11 /*
  12  * Copyright 2014 Nexenta Systems, Inc.
  13  */
  14 
  15 #include <stdio.h>
  16 #include <stdlib.h>
  17 #include <strings.h>
  18 #include <fcntl.h>
  19 #include <security/pam_appl.h>
  20 #include <security/pam_modules.h>
  21 #include <security/pam_impl.h>
  22 #include <sys/param.h>
  23 #include <sys/stat.h>
  24 #include <sys/types.h>
  25 #include <syslog.h>
  26 #include <unistd.h>
  27 #include <libgen.h>
  28 #include <errno.h>
  29 
  30 #define TIMESTAMP_DIR           "/var/run/tty_timestamps"
  31 #define TIMESTAMP_TIMEOUT       5 /* default timeout */
  32 #define ROOT_UID                0 /* root uid */
  33 #define ROOT_GID                0 /* root gid */
  34 
  35 struct user_info {
  36         dev_t dev;              /* ID of device tty resides on */
  37         dev_t rdev;             /* tty device ID */
  38         ino_t ino;              /* tty inode number */
  39         uid_t uid;              /* user's uid */
  40         pid_t ppid;             /* parent pid */
  41         pid_t sid;              /* session ID associated with tty/ppid */
  42         timestruc_t ts;         /* time of tty last status change */
  43 };
  44 
  45 int debug = 0;
  46 
  47 int
  48 validate_basic(
  49         pam_handle_t            *pamh,
  50         char                    *user_tty,
  51         char                    *timestampfile)
  52 {
  53         char                    *user;
  54         char                    *auser;
  55         char                    *ttyn;
  56 
  57         /* get user, auser and users's tty */
  58         (void) pam_get_item(pamh, PAM_USER, (void **)&user);
  59         (void) pam_get_item(pamh, PAM_AUSER, (void **)&auser);
  60         (void) pam_get_item(pamh, PAM_TTY, (void **)&ttyn);
  61 
  62         if (user == NULL || *user == '\0') {
  63                 syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
  64                 "PAM_USER NULL or empty");
  65                 return (PAM_IGNORE);
  66         }
  67 
  68         if (auser == NULL || *auser == '\0') {
  69                 syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
  70                 "PAM_AUSER NULL or empty");
  71                 return (PAM_IGNORE);
  72         }
  73 
  74         if (ttyn == NULL || *ttyn == '\0') {
  75                 syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
  76                 "PAM_TTY NULL or empty");
  77                 return (PAM_IGNORE);
  78         }
  79 
  80         if (debug)
  81                 syslog(LOG_AUTH | LOG_DEBUG, "pam_timestamp: "
  82                 "user = %s, auser = %s, tty = %s", user, auser, ttyn);
  83 
  84         (void) strlcpy(user_tty, ttyn, MAXPATHLEN);
  85 
  86         if (strchr(ttyn, '/') == NULL || strncmp(ttyn, "/dev/", 5) == 0) {
  87                 ttyn = strrchr(ttyn, '/') + 1;
  88         } else {
  89                 syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
  90                 "invalid tty: %s", ttyn);
  91                 return (PAM_IGNORE);
  92         }
  93 
  94         /* format timestamp file name */
  95         (void) snprintf(timestampfile, MAXPATHLEN, "%s/%s/%s:%s", TIMESTAMP_DIR,
  96             auser, ttyn, user);
  97 
  98         return (PAM_SUCCESS);
  99 }
 100 
 101 int
 102 validate_dir(const char *dir)
 103 {
 104         struct          stat sb;
 105 
 106         /*
 107          * check that the directory exist and has
 108          * right owner and permissions.
 109          */
 110         if (lstat(dir, &sb) < 0) {
 111                 syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
 112                     "directory %s does not exist", dir);
 113                 return (PAM_IGNORE);
 114         }
 115 
 116         if (!S_ISDIR(sb.st_mode)) {
 117                 syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
 118                     "%s is not a directory", dir);
 119                 return (PAM_IGNORE);
 120         }
 121 
 122         if (S_ISLNK(sb.st_mode)) {
 123                 syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
 124                     "%s is a symbolic link", dir);
 125                 return (PAM_IGNORE);
 126         }
 127 
 128         if (sb.st_uid != 0 || sb.st_gid != 0) {
 129                 syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
 130                     "%s is not owned by root", dir);
 131                 return (PAM_IGNORE);
 132         }
 133 
 134         if (sb.st_mode & (S_IWGRP | S_IWOTH | S_IROTH)) {
 135                 syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
 136                     "%s has wrong permissions", dir);
 137                 return (PAM_IGNORE);
 138         }
 139 
 140         return (PAM_SUCCESS);
 141 }
 142 
 143 int
 144 create_dir(char *dir)
 145 {
 146         /*
 147          * create directory if it doesn't exist and attempt to set
 148          * the owner to root.
 149          */
 150         if (mkdir(dir, S_IRWXU) < 0) {
 151                 if (errno != EEXIST) {
 152                         syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
 153                             "can't create directory %s", dir);
 154                         return (PAM_IGNORE);
 155                 }
 156         } else if (lchown(dir, ROOT_UID, ROOT_GID) < 0) {
 157                 syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
 158                     "can't set permissions on directory %s", dir);
 159                 return (PAM_IGNORE);
 160         }
 161         return (PAM_SUCCESS);
 162 }
 163 
 164 /*
 165  * pam_sm_authenticate
 166  *
 167  * Read authentication from user, using cached successful authentication
 168  * attempts.
 169  *
 170  * returns PAM_SUCCESS on success, otherwise always returns PAM_IGNORE:
 171  * while this module has "sufficient" control value, in case of any failure
 172  * user will be authenticated with the pam_unix_auth module.
 173  * options -
 174  *      debug
 175  *      timeout=        timeout in min, default is 5
 176  */
 177 /*ARGSUSED*/
 178 int
 179 pam_sm_authenticate(
 180         pam_handle_t            *pamh,
 181         int                     flags,
 182         int                     argc,
 183         const char              **argv)
 184 {
 185         struct                  user_info info;
 186         struct                  stat sb, tty;
 187         time_t                  timeout = 0;
 188         long                    tmp = 0;
 189         int                     result = PAM_IGNORE;
 190         int                     i;
 191         int                     fd = -1;
 192         char                    *p;
 193         char                    user_tty[MAXPATHLEN];
 194         char                    timestampdir[MAXPATHLEN];
 195         char                    timestampfile[MAXPATHLEN];
 196         char                    *sudir;
 197 
 198         timeout = TIMESTAMP_TIMEOUT;
 199 
 200         /* check options passed to this module */
 201         for (i = 0; i < argc; i++) {
 202                 if (strcmp(argv[i], "debug") == 0) {
 203                         debug = 1;
 204                 } else if (strncmp(argv[i], "timeout=", 8) == 0) {
 205                         tmp = strtol(argv[i] + 8, &p, 0);
 206                         if ((p != NULL) && (*p == '\0') && tmp > 0) {
 207                                 timeout = tmp;
 208                         }
 209                 }
 210         }
 211 
 212         if (validate_basic(pamh, user_tty, timestampfile) != PAM_SUCCESS)
 213                 return (result);
 214 
 215         sudir = TIMESTAMP_DIR;
 216         if (validate_dir(sudir) != PAM_SUCCESS)
 217                 return (result);
 218 
 219         (void) strlcpy(timestampdir, timestampfile, MAXPATHLEN);
 220 
 221         if (validate_dir(dirname(timestampdir)) != PAM_SUCCESS)
 222                 return (result);
 223 
 224         /*
 225          * check that timestamp file is exist and has right owner
 226          * and permissions.
 227          */
 228         if (lstat(timestampfile, &sb) == 0 && sb.st_size != 0) {
 229                 if (!S_ISREG(sb.st_mode)) {
 230                         (void) unlink(timestampfile);
 231                         syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
 232                             "timestamp file %s is not a regular file",
 233                             timestampfile);
 234                         return (result);
 235                 }
 236 
 237                 if (sb.st_uid != 0 || sb.st_gid != 0) {
 238                         (void) unlink(timestampfile);
 239                         syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
 240                             "timestamp file %s is not owned by root",
 241                             timestampfile);
 242                         return (result);
 243                 }
 244 
 245                 if (sb.st_nlink != 1 || S_ISLNK(sb.st_mode)) {
 246                         (void) unlink(timestampfile);
 247                         syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
 248                             "timestamp file %s is a symbolic link",
 249                             timestampfile);
 250                         return (result);
 251                 }
 252 
 253                 if (sb.st_mode & (S_IRWXG | S_IRWXO)) {
 254                         (void) unlink(timestampfile);
 255                         syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
 256                             "timestamp file %s has wrong permissions",
 257                             timestampfile);
 258                         return (result);
 259                 }
 260         } else {
 261                 if (debug)
 262                         syslog(LOG_AUTH | LOG_DEBUG, "pam_timestamp: "
 263                             "timestamp file %s does not exist: %m",
 264                             timestampfile);
 265                 return (result);
 266         }
 267 
 268 
 269         if (stat(user_tty, &tty) < 0) {
 270                 syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
 271                     "can't stat tty: %m");
 272                 return (result);
 273         }
 274 
 275         if ((fd = open(timestampfile, O_RDONLY)) < 0) {
 276                 syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
 277                     "can't open timestamp file %s for reading: %m",
 278                     timestampfile);
 279                 return (result);
 280         }
 281 
 282         if (read(fd, &info, sizeof (info)) != sizeof (info)) {
 283                 (void) close(fd);
 284                 (void) unlink(timestampfile);
 285                 syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
 286                     "timestamp file '%s' is corrupt: %m", timestampfile);
 287                 return (result);
 288         }
 289 
 290         if (info.dev != tty.st_dev || info.ino != tty.st_ino ||
 291             info.rdev != tty.st_rdev || info.sid != getsid(getpid()) ||
 292             info.uid != getuid() || info.ts.tv_sec != tty.st_ctim.tv_sec ||
 293             info.ts.tv_nsec != tty.st_ctim.tv_nsec) {
 294                 (void) close(fd);
 295                 (void) unlink(timestampfile);
 296                 syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
 297                     "the content of the timestamp file '%s' is not valid",
 298                     timestampfile);
 299                 return (result);
 300         }
 301 
 302         if (time((time_t *)0) - sb.st_mtime > 60 * timeout) {
 303                 (void) unlink(timestampfile);
 304                 syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
 305                     "timestamp file '%s' has expired, disallowing access",
 306                     timestampfile);
 307                 return (result);
 308         } else {
 309                 if (debug)
 310                         syslog(LOG_AUTH | LOG_DEBUG, "pam_timestamp: "
 311                             "timestamp file %s is not expired, "
 312                             "allowing access ", timestampfile);
 313                 result = PAM_SUCCESS;
 314         }
 315 
 316         return (result);
 317 }
 318 
 319 /*
 320  * pam_sm_setcred
 321  *
 322  * Creates timestamp directory and writes
 323  * timestamp file if it doesn't exist.
 324  *
 325  * returns PAM_SUCCESS on success, otherwise PAM_IGNORE
 326  */
 327 /*ARGSUSED*/
 328 int
 329 pam_sm_setcred(
 330         pam_handle_t            *pamh,
 331         int                     flags,
 332         int                     argc,
 333         const char              **argv)
 334 {
 335         struct                  stat sb;
 336         struct                  stat tty;
 337         struct                  user_info info;
 338         int                     result = PAM_IGNORE;
 339         int                     fd = -1;
 340         char                    user_tty[MAXPATHLEN];
 341         char                    timestampdir[MAXPATHLEN];
 342         char                    timestampfile[MAXPATHLEN];
 343 
 344         /* validate flags */
 345         if (flags && !(flags & PAM_ESTABLISH_CRED) &&
 346             !(flags & PAM_REINITIALIZE_CRED) &&
 347             !(flags & PAM_REFRESH_CRED) &&
 348             !(flags & PAM_DELETE_CRED) &&
 349             !(flags & PAM_SILENT)) {
 350                 syslog(LOG_ERR, "pam_timestamp: illegal flag %d", flags);
 351                 return (result);
 352         }
 353 
 354         if (validate_basic(pamh, user_tty, timestampfile) != PAM_SUCCESS)
 355                 return (result);
 356 
 357         /*
 358          * user doesn't need to authenticate for PAM_DELETE_CRED
 359          */
 360         if (flags & PAM_DELETE_CRED) {
 361                 (void) unlink(timestampfile);
 362                 return (result);
 363         }
 364 
 365         /* if the timestamp file exist, there is nothing to do */
 366         if (lstat(timestampfile, &sb) == 0) {
 367                 if (debug)
 368                         syslog(LOG_AUTH | LOG_DEBUG, "pam_timestamp: "
 369                             "timestamp file %s is not expired", timestampfile);
 370                 return (result);
 371         }
 372 
 373         if (create_dir(TIMESTAMP_DIR) != PAM_SUCCESS)
 374                 return (result);
 375 
 376         (void) strlcpy(timestampdir, timestampfile, MAXPATHLEN);
 377 
 378         if (create_dir(dirname(timestampdir)) != PAM_SUCCESS)
 379                 return (result);
 380 
 381         if (stat(user_tty, &tty) < 0) {
 382                 syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
 383                     "can't stat tty: %m");
 384                 return (result);
 385         }
 386 
 387         info.dev = tty.st_dev;
 388         info.ino = tty.st_ino;
 389         info.rdev = tty.st_rdev;
 390         info.sid = getsid(getpid());
 391         info.uid = getuid();
 392         info.ts = tty.st_ctim;
 393 
 394         if ((fd = open(timestampfile, O_WRONLY|O_CREAT, S_IRUSR|S_IWUSR)) < 0) {
 395                 syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
 396                     "can't open timestamp file %s for writing: %m",
 397                     timestampfile);
 398                 return (result);
 399         } else if (fchown(fd, ROOT_UID, ROOT_GID) != 0) {
 400                 syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
 401                     "can't set permissions on timestamp file %s: %m",
 402                     timestampfile);
 403                 (void) close(fd);
 404                 return (result);
 405         }
 406 
 407         if (write(fd, &info, sizeof (info)) != sizeof (info)) {
 408                 (void) close(fd);
 409                 syslog(LOG_AUTH | LOG_ERR, "pam_timestamp: "
 410                     "can't write timestamp file %s: %m", timestampfile);
 411                 return (result);
 412         }
 413         (void) close(fd);
 414 
 415         return (PAM_SUCCESS);
 416 }