Home | History | Annotate | Line # | Download | only in dist
      1 /*
      2  *  Copyright (C) 2014-2023 Yubico AB - See COPYING
      3  */
      4 
      5 /* Define which PAM interfaces we provide */
      6 #define PAM_SM_AUTH
      7 
      8 /* Include PAM headers */
      9 #include <security/pam_appl.h>
     10 #include <security/pam_modules.h>
     11 
     12 #include <fcntl.h>
     13 #include <sys/types.h>
     14 #include <sys/stat.h>
     15 #include <unistd.h>
     16 #include <stdint.h>
     17 #include <stdlib.h>
     18 #include <syslog.h>
     19 #include <pwd.h>
     20 #include <string.h>
     21 #include <errno.h>
     22 
     23 #include "debug.h"
     24 #include "drop_privs.h"
     25 #include "util.h"
     26 
     27 #define free_const(a) free((void *) (uintptr_t) (a))
     28 
     29 /* If secure_getenv is not defined, define it here */
     30 #ifndef HAVE_SECURE_GETENV
     31 char *secure_getenv(const char *);
     32 char *secure_getenv(const char *name) {
     33   (void) name;
     34   return NULL;
     35 }
     36 #endif
     37 
     38 static void parse_cfg(int flags __unused, int argc, const char **argv, cfg_t *cfg) {
     39   int i;
     40 
     41   memset(cfg, 0, sizeof(cfg_t));
     42   cfg->debug_file = DEFAULT_DEBUG_FILE;
     43   cfg->userpresence = -1;
     44   cfg->userverification = -1;
     45   cfg->pinverification = -1;
     46 
     47   for (i = 0; i < argc; i++) {
     48     if (strncmp(argv[i], "max_devices=", 12) == 0) {
     49       sscanf(argv[i], "max_devices=%u", &cfg->max_devs);
     50     } else if (strcmp(argv[i], "manual") == 0) {
     51       cfg->manual = 1;
     52     } else if (strcmp(argv[i], "debug") == 0) {
     53       cfg->debug = 1;
     54     } else if (strcmp(argv[i], "nouserok") == 0) {
     55       cfg->nouserok = 1;
     56     } else if (strcmp(argv[i], "openasuser") == 0) {
     57       cfg->openasuser = 1;
     58     } else if (strcmp(argv[i], "alwaysok") == 0) {
     59       cfg->alwaysok = 1;
     60     } else if (strcmp(argv[i], "interactive") == 0) {
     61       cfg->interactive = 1;
     62     } else if (strcmp(argv[i], "cue") == 0) {
     63       cfg->cue = 1;
     64     } else if (strcmp(argv[i], "nodetect") == 0) {
     65       cfg->nodetect = 1;
     66     } else if (strcmp(argv[i], "expand") == 0) {
     67       cfg->expand = 1;
     68     } else if (strncmp(argv[i], "userpresence=", 13) == 0) {
     69       sscanf(argv[i], "userpresence=%d", &cfg->userpresence);
     70     } else if (strncmp(argv[i], "userverification=", 17) == 0) {
     71       sscanf(argv[i], "userverification=%d", &cfg->userverification);
     72     } else if (strncmp(argv[i], "pinverification=", 16) == 0) {
     73       sscanf(argv[i], "pinverification=%d", &cfg->pinverification);
     74     } else if (strncmp(argv[i], "authfile=", 9) == 0) {
     75       cfg->auth_file = argv[i] + 9;
     76     } else if (strcmp(argv[i], "sshformat") == 0) {
     77       cfg->sshformat = 1;
     78     } else if (strncmp(argv[i], "authpending_file=", 17) == 0) {
     79       cfg->authpending_file = argv[i] + 17;
     80     } else if (strncmp(argv[i], "origin=", 7) == 0) {
     81       cfg->origin = argv[i] + 7;
     82     } else if (strncmp(argv[i], "appid=", 6) == 0) {
     83       cfg->appid = argv[i] + 6;
     84     } else if (strncmp(argv[i], "prompt=", 7) == 0) {
     85       cfg->prompt = argv[i] + 7;
     86     } else if (strncmp(argv[i], "cue_prompt=", 11) == 0) {
     87       cfg->cue_prompt = argv[i] + 11;
     88     } else if (strncmp(argv[i], "debug_file=", 11) == 0) {
     89       const char *filename = argv[i] + 11;
     90       debug_close(cfg->debug_file);
     91       cfg->debug_file = debug_open(filename);
     92     }
     93   }
     94 
     95   debug_dbg(cfg, "called.");
     96   debug_dbg(cfg, "flags %d argc %d", flags, argc);
     97   for (i = 0; i < argc; i++) {
     98     debug_dbg(cfg, "argv[%d]=%s", i, argv[i]);
     99   }
    100   debug_dbg(cfg, "max_devices=%d", cfg->max_devs);
    101   debug_dbg(cfg, "debug=%d", cfg->debug);
    102   debug_dbg(cfg, "interactive=%d", cfg->interactive);
    103   debug_dbg(cfg, "cue=%d", cfg->cue);
    104   debug_dbg(cfg, "nodetect=%d", cfg->nodetect);
    105   debug_dbg(cfg, "userpresence=%d", cfg->userpresence);
    106   debug_dbg(cfg, "userverification=%d", cfg->userverification);
    107   debug_dbg(cfg, "pinverification=%d", cfg->pinverification);
    108   debug_dbg(cfg, "manual=%d", cfg->manual);
    109   debug_dbg(cfg, "nouserok=%d", cfg->nouserok);
    110   debug_dbg(cfg, "openasuser=%d", cfg->openasuser);
    111   debug_dbg(cfg, "alwaysok=%d", cfg->alwaysok);
    112   debug_dbg(cfg, "sshformat=%d", cfg->sshformat);
    113   debug_dbg(cfg, "expand=%d", cfg->expand);
    114   debug_dbg(cfg, "authfile=%s", cfg->auth_file ? cfg->auth_file : "(null)");
    115   debug_dbg(cfg, "authpending_file=%s",
    116             cfg->authpending_file ? cfg->authpending_file : "(null)");
    117   debug_dbg(cfg, "origin=%s", cfg->origin ? cfg->origin : "(null)");
    118   debug_dbg(cfg, "appid=%s", cfg->appid ? cfg->appid : "(null)");
    119   debug_dbg(cfg, "prompt=%s", cfg->prompt ? cfg->prompt : "(null)");
    120 }
    121 
    122 static void interactive_prompt(pam_handle_t *pamh, const cfg_t *cfg) {
    123   char *tmp = NULL;
    124 
    125   tmp = converse(pamh, PAM_PROMPT_ECHO_ON,
    126                  cfg->prompt != NULL ? cfg->prompt : DEFAULT_PROMPT);
    127 
    128   free(tmp);
    129 }
    130 
    131 static char *resolve_authfile_path(const cfg_t *cfg, const struct passwd *user,
    132                                    int *openasuser) {
    133   char *authfile = NULL;
    134   const char *dir = NULL;
    135   const char *path = NULL;
    136 
    137   *openasuser = geteuid() == 0; /* user files, drop privileges */
    138 
    139   if (cfg->auth_file == NULL) {
    140     if ((dir = secure_getenv(DEFAULT_AUTHFILE_DIR_VAR)) == NULL) {
    141       debug_dbg(cfg, "Variable %s is not set, using default",
    142                 DEFAULT_AUTHFILE_DIR_VAR);
    143       dir = user->pw_dir;
    144       path = cfg->sshformat ? DEFAULT_AUTHFILE_DIR_SSH "/" DEFAULT_AUTHFILE_SSH
    145                             : DEFAULT_AUTHFILE_DIR "/" DEFAULT_AUTHFILE;
    146     } else {
    147       debug_dbg(cfg, "Variable %s set to %s", DEFAULT_AUTHFILE_DIR_VAR, dir);
    148       *openasuser = 0; /* documented exception, require explicit openasuser */
    149       path = cfg->sshformat ? DEFAULT_AUTHFILE_SSH : DEFAULT_AUTHFILE;
    150       if (!cfg->openasuser) {
    151         debug_dbg(cfg, "WARNING: not dropping privileges when reading the "
    152                        "authentication file, please consider setting "
    153                        "openasuser=1 in the module configuration");
    154       }
    155     }
    156   } else {
    157     dir = user->pw_dir;
    158     path = cfg->auth_file;
    159   }
    160 
    161   if (dir == NULL || *dir != '/' || path == NULL ||
    162       asprintf(&authfile, "%s/%s", dir, path) == -1)
    163     authfile = NULL;
    164 
    165   return authfile;
    166 }
    167 
    168 /* PAM entry point for authentication verification */
    169 int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc,
    170                         const char **argv) {
    171 
    172   struct passwd *pw = NULL, pw_s;
    173   const char *user = NULL;
    174 
    175   cfg_t cfg_st;
    176   cfg_t *cfg = &cfg_st;
    177   char buffer[BUFSIZE];
    178   int pgu_ret, gpn_ret;
    179   int retval = PAM_ABORT;
    180   device_t *devices = NULL;
    181   unsigned n_devices = 0;
    182   int openasuser = 0;
    183   int should_free_origin = 0;
    184   int should_free_appid = 0;
    185   int should_free_auth_file = 0;
    186   int should_free_authpending_file = 0;
    187 
    188   parse_cfg(flags, argc, argv, cfg);
    189 
    190   PAM_MODUTIL_DEF_PRIVS(privs);
    191 
    192   if (!cfg->origin) {
    193     if (!cfg->sshformat) {
    194       strcpy(buffer, DEFAULT_ORIGIN_PREFIX);
    195       if (gethostname(buffer + strlen(DEFAULT_ORIGIN_PREFIX),
    196                       BUFSIZE - strlen(DEFAULT_ORIGIN_PREFIX)) == -1) {
    197         debug_dbg(cfg, "Unable to get host name");
    198         retval = PAM_SYSTEM_ERR;
    199         goto done;
    200       }
    201     } else {
    202       strcpy(buffer, SSH_ORIGIN);
    203     }
    204     debug_dbg(cfg, "Origin not specified, using \"%s\"", buffer);
    205     cfg->origin = strdup(buffer);
    206     if (!cfg->origin) {
    207       debug_dbg(cfg, "Unable to allocate memory");
    208       retval = PAM_BUF_ERR;
    209       goto done;
    210     } else {
    211       should_free_origin = 1;
    212     }
    213   }
    214 
    215   if (!cfg->appid) {
    216     debug_dbg(cfg, "Appid not specified, using the value of origin (%s)",
    217               cfg->origin);
    218     cfg->appid = strdup(cfg->origin);
    219     if (!cfg->appid) {
    220       debug_dbg(cfg, "Unable to allocate memory");
    221       retval = PAM_BUF_ERR;
    222       goto done;
    223     } else {
    224       should_free_appid = 1;
    225     }
    226   }
    227 
    228   if (cfg->max_devs == 0) {
    229     debug_dbg(cfg, "Maximum number of devices not set. Using default (%d)",
    230               MAX_DEVS);
    231     cfg->max_devs = MAX_DEVS;
    232   }
    233 #if WITH_FUZZING
    234   if (cfg->max_devs > 256)
    235     cfg->max_devs = 256;
    236 #endif
    237 
    238   devices = calloc(cfg->max_devs, sizeof(device_t));
    239   if (!devices) {
    240     debug_dbg(cfg, "Unable to allocate memory");
    241     retval = PAM_BUF_ERR;
    242     goto done;
    243   }
    244 
    245   pgu_ret = pam_get_user(pamh, &user, NULL);
    246   if (pgu_ret != PAM_SUCCESS || user == NULL) {
    247     debug_dbg(cfg, "Unable to get username from PAM");
    248     retval = PAM_CONV_ERR;
    249     goto done;
    250   }
    251 
    252   debug_dbg(cfg, "Requesting authentication for user %s", user);
    253 
    254   gpn_ret = getpwnam_r(user, &pw_s, buffer, sizeof(buffer), &pw);
    255   if (gpn_ret != 0 || pw == NULL || pw->pw_dir == NULL ||
    256       pw->pw_dir[0] != '/') {
    257     debug_dbg(cfg, "Unable to retrieve credentials for user %s, (%s)", user,
    258               strerror(errno));
    259     retval = PAM_SYSTEM_ERR;
    260     goto done;
    261   }
    262 
    263   debug_dbg(cfg, "Found user %s", user);
    264   debug_dbg(cfg, "Home directory for %s is %s", user, pw->pw_dir);
    265 
    266   // Perform variable expansion.
    267   if (cfg->expand && cfg->auth_file) {
    268     if ((cfg->auth_file = expand_variables(cfg->auth_file, user)) == NULL) {
    269       debug_dbg(cfg, "Failed to perform variable expansion");
    270       retval = PAM_BUF_ERR;
    271       goto done;
    272     }
    273     should_free_auth_file = 1;
    274   }
    275   // Resolve default or relative paths.
    276   if (!cfg->auth_file || cfg->auth_file[0] != '/') {
    277     char *tmp = resolve_authfile_path(cfg, pw, &openasuser);
    278     if (tmp == NULL) {
    279       debug_dbg(cfg, "Could not resolve authfile path");
    280       retval = PAM_BUF_ERR;
    281       goto done;
    282     }
    283     if (should_free_auth_file) {
    284       free_const(cfg->auth_file);
    285     }
    286     cfg->auth_file = tmp;
    287     should_free_auth_file = 1;
    288   }
    289 
    290   debug_dbg(cfg, "Using authentication file %s", cfg->auth_file);
    291 
    292   if (!openasuser) {
    293     openasuser = geteuid() == 0 && cfg->openasuser;
    294   }
    295   if (openasuser) {
    296     debug_dbg(cfg, "Dropping privileges");
    297     if (pam_modutil_drop_priv(pamh, &privs, pw)) {
    298       debug_dbg(cfg, "Unable to switch user to uid %i", pw->pw_uid);
    299       retval = PAM_SYSTEM_ERR;
    300       goto done;
    301     }
    302     debug_dbg(cfg, "Switched to uid %i", pw->pw_uid);
    303   }
    304   retval = get_devices_from_authfile(cfg, user, devices, &n_devices);
    305 
    306   if (openasuser) {
    307     if (pam_modutil_regain_priv(pamh, &privs)) {
    308       debug_dbg(cfg, "could not restore privileges");
    309       retval = PAM_SYSTEM_ERR;
    310       goto done;
    311     }
    312     debug_dbg(cfg, "Restored privileges");
    313   }
    314 
    315   if (retval != PAM_SUCCESS) {
    316     goto done;
    317   }
    318 
    319   // Determine the full path for authpending_file in order to emit touch request
    320   // notifications
    321   if (!cfg->authpending_file) {
    322     int actual_size =
    323       snprintf(buffer, BUFSIZE, DEFAULT_AUTHPENDING_FILE_PATH, getuid());
    324     if (actual_size >= 0 && actual_size < BUFSIZE) {
    325       cfg->authpending_file = strdup(buffer);
    326     }
    327     if (!cfg->authpending_file) {
    328       debug_dbg(cfg, "Unable to allocate memory for the authpending_file, "
    329                      "touch request notifications will not be emitted");
    330     } else {
    331       should_free_authpending_file = 1;
    332     }
    333   } else {
    334     if (strlen(cfg->authpending_file) == 0) {
    335       debug_dbg(cfg, "authpending_file is set to an empty value, touch request "
    336                      "notifications will be disabled");
    337       cfg->authpending_file = NULL;
    338     }
    339   }
    340 
    341   int authpending_file_descriptor = -1;
    342   if (cfg->authpending_file) {
    343     debug_dbg(cfg, "Touch request notifications will be emitted via '%s'",
    344               cfg->authpending_file);
    345 
    346     // Open (or create) the authpending_file to indicate that we start waiting
    347     // for a touch
    348     authpending_file_descriptor =
    349       open(cfg->authpending_file,
    350            O_RDONLY | O_CREAT | O_CLOEXEC | O_NOFOLLOW | O_NOCTTY, 0664);
    351     if (authpending_file_descriptor < 0) {
    352       debug_dbg(cfg, "Unable to emit 'authentication started' notification: %s",
    353                 strerror(errno));
    354     }
    355   }
    356 
    357   if (cfg->manual == 0) {
    358     if (cfg->interactive) {
    359       interactive_prompt(pamh, cfg);
    360     }
    361     retval = do_authentication(cfg, devices, n_devices, pamh);
    362   } else {
    363     retval = do_manual_authentication(cfg, devices, n_devices, pamh);
    364   }
    365 
    366   // Close the authpending_file to indicate that we stop waiting for a touch
    367   if (authpending_file_descriptor >= 0) {
    368     if (close(authpending_file_descriptor) < 0) {
    369       debug_dbg(cfg, "Unable to emit 'authentication stopped' notification: %s",
    370                 strerror(errno));
    371     }
    372   }
    373 
    374 done:
    375   free_devices(devices, n_devices);
    376 
    377   if (should_free_origin) {
    378     free_const(cfg->origin);
    379     cfg->origin = NULL;
    380   }
    381 
    382   if (should_free_appid) {
    383     free_const(cfg->appid);
    384     cfg->appid = NULL;
    385   }
    386 
    387   if (should_free_auth_file) {
    388     free_const(cfg->auth_file);
    389     cfg->auth_file = NULL;
    390   }
    391 
    392   if (should_free_authpending_file) {
    393     free_const(cfg->authpending_file);
    394     cfg->authpending_file = NULL;
    395   }
    396 
    397   if (cfg->alwaysok && retval != PAM_SUCCESS) {
    398     debug_dbg(cfg, "alwaysok needed (otherwise return with %d)", retval);
    399     retval = PAM_SUCCESS;
    400   }
    401   debug_dbg(cfg, "done. [%s]", pam_strerror(pamh, retval));
    402 
    403   debug_close(cfg->debug_file);
    404   cfg->debug_file = DEFAULT_DEBUG_FILE;
    405 
    406   return retval;
    407 }
    408 
    409 PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc,
    410                               const char **argv) {
    411   (void) pamh;
    412   (void) flags;
    413   (void) argc;
    414   (void) argv;
    415 
    416   return PAM_SUCCESS;
    417 }
    418 
    419 #ifdef PAM_MODULE_ENTRY
    420 PAM_MODULE_ENTRY("pam_u2f");
    421 #endif
    422