pam-u2f.c revision 1.1
1/*
2 *  Copyright (C) 2014-2019 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 <stdlib.h>
17#include <syslog.h>
18#include <pwd.h>
19#include <string.h>
20#include <errno.h>
21
22#include "util.h"
23#include "drop_privs.h"
24
25/* If secure_getenv is not defined, define it here */
26#ifndef HAVE_SECURE_GETENV
27char *secure_getenv(const char *);
28char *secure_getenv(const char *name) {
29  (void) name;
30  return NULL;
31}
32#endif
33
34static void parse_cfg(int flags, int argc, const char **argv, cfg_t *cfg) {
35  struct stat st;
36  FILE *file = NULL;
37  int fd = -1;
38  int i;
39
40  memset(cfg, 0, sizeof(cfg_t));
41  cfg->debug_file = stderr;
42  cfg->userpresence = -1;
43  cfg->userverification = -1;
44  cfg->pinverification = -1;
45
46  for (i = 0; i < argc; i++) {
47    if (strncmp(argv[i], "max_devices=", 12) == 0)
48      sscanf(argv[i], "max_devices=%u", &cfg->max_devs);
49    if (strcmp(argv[i], "manual") == 0)
50      cfg->manual = 1;
51    if (strcmp(argv[i], "debug") == 0)
52      cfg->debug = 1;
53    if (strcmp(argv[i], "nouserok") == 0)
54      cfg->nouserok = 1;
55    if (strcmp(argv[i], "openasuser") == 0)
56      cfg->openasuser = 1;
57    if (strcmp(argv[i], "alwaysok") == 0)
58      cfg->alwaysok = 1;
59    if (strcmp(argv[i], "interactive") == 0)
60      cfg->interactive = 1;
61    if (strcmp(argv[i], "cue") == 0)
62      cfg->cue = 1;
63    if (strcmp(argv[i], "nodetect") == 0)
64      cfg->nodetect = 1;
65    if (strncmp(argv[i], "userpresence=", 13) == 0)
66      sscanf(argv[i], "userpresence=%d", &cfg->userpresence);
67    if (strncmp(argv[i], "userverification=", 17) == 0)
68      sscanf(argv[i], "userverification=%d", &cfg->userverification);
69    if (strncmp(argv[i], "pinverification=", 16) == 0)
70      sscanf(argv[i], "pinverification=%d", &cfg->pinverification);
71    if (strncmp(argv[i], "authfile=", 9) == 0)
72      cfg->auth_file = argv[i] + 9;
73    if (strncmp(argv[i], "authpending_file=", 17) == 0)
74      cfg->authpending_file = argv[i] + 17;
75    if (strncmp(argv[i], "origin=", 7) == 0)
76      cfg->origin = argv[i] + 7;
77    if (strncmp(argv[i], "appid=", 6) == 0)
78      cfg->appid = argv[i] + 6;
79    if (strncmp(argv[i], "prompt=", 7) == 0)
80      cfg->prompt = argv[i] + 7;
81    if (strncmp(argv[i], "cue_prompt=", 11) == 0)
82      cfg->cue_prompt = argv[i] + 11;
83    if (strncmp(argv[i], "debug_file=", 11) == 0) {
84      const char *filename = argv[i] + 11;
85      if (strncmp(filename, "stdout", 6) == 0) {
86        cfg->debug_file = stdout;
87      } else if (strncmp(filename, "stderr", 6) == 0) {
88        cfg->debug_file = stderr;
89      } else if (strncmp(filename, "syslog", 6) == 0) {
90        cfg->debug_file = (FILE *) -1;
91      } else {
92        fd = open(filename,
93                  O_WRONLY | O_APPEND | O_CLOEXEC | O_NOFOLLOW | O_NOCTTY);
94        if (fd >= 0 && (fstat(fd, &st) == 0) && S_ISREG(st.st_mode)) {
95          file = fdopen(fd, "a");
96          if (file != NULL) {
97            cfg->debug_file = file;
98            cfg->is_custom_debug_file = 1;
99            file = NULL;
100            fd = -1;
101          }
102        }
103      }
104    }
105  }
106
107  if (cfg->debug) {
108    D(cfg->debug_file, "called.");
109    D(cfg->debug_file, "flags %d argc %d", flags, argc);
110    for (i = 0; i < argc; i++) {
111      D(cfg->debug_file, "argv[%d]=%s", i, argv[i]);
112    }
113    D(cfg->debug_file, "max_devices=%d", cfg->max_devs);
114    D(cfg->debug_file, "debug=%d", cfg->debug);
115    D(cfg->debug_file, "interactive=%d", cfg->interactive);
116    D(cfg->debug_file, "cue=%d", cfg->cue);
117    D(cfg->debug_file, "nodetect=%d", cfg->nodetect);
118    D(cfg->debug_file, "userpresence=%d", cfg->userpresence);
119    D(cfg->debug_file, "userverification=%d", cfg->userverification);
120    D(cfg->debug_file, "pinverification=%d", cfg->pinverification);
121    D(cfg->debug_file, "manual=%d", cfg->manual);
122    D(cfg->debug_file, "nouserok=%d", cfg->nouserok);
123    D(cfg->debug_file, "openasuser=%d", cfg->openasuser);
124    D(cfg->debug_file, "alwaysok=%d", cfg->alwaysok);
125    D(cfg->debug_file, "authfile=%s",
126      cfg->auth_file ? cfg->auth_file : "(null)");
127    D(cfg->debug_file, "authpending_file=%s",
128      cfg->authpending_file ? cfg->authpending_file : "(null)");
129    D(cfg->debug_file, "origin=%s", cfg->origin ? cfg->origin : "(null)");
130    D(cfg->debug_file, "appid=%s", cfg->appid ? cfg->appid : "(null)");
131    D(cfg->debug_file, "prompt=%s", cfg->prompt ? cfg->prompt : "(null)");
132  }
133
134  if (fd != -1)
135    close(fd);
136
137  if (file != NULL)
138    fclose(file);
139}
140
141#ifdef DBG
142#undef DBG
143#endif
144#define DBG(...)                                                               \
145  if (cfg->debug) {                                                            \
146    D(cfg->debug_file, __VA_ARGS__);                                           \
147  }
148
149/* PAM entry point for authentication verification */
150int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc,
151                        const char **argv) {
152
153  struct passwd *pw = NULL, pw_s;
154  const char *user = NULL;
155
156  cfg_t cfg_st;
157  cfg_t *cfg = &cfg_st;
158  char buffer[BUFSIZE];
159  char *buf = NULL;
160  char *authfile_dir;
161  size_t authfile_dir_len;
162  int pgu_ret, gpn_ret;
163  int retval = PAM_IGNORE;
164  device_t *devices = NULL;
165  unsigned n_devices = 0;
166  int openasuser = 0;
167  int should_free_origin = 0;
168  int should_free_appid = 0;
169  int should_free_auth_file = 0;
170  int should_free_authpending_file = 0;
171  PAM_MODUTIL_DEF_PRIVS(privs);
172
173  parse_cfg(flags, argc, argv, cfg);
174
175  if (!cfg->origin) {
176    strcpy(buffer, DEFAULT_ORIGIN_PREFIX);
177
178    if (gethostname(buffer + strlen(DEFAULT_ORIGIN_PREFIX),
179                    BUFSIZE - strlen(DEFAULT_ORIGIN_PREFIX)) == -1) {
180      DBG("Unable to get host name");
181      goto done;
182    }
183    DBG("Origin not specified, using \"%s\"", buffer);
184    cfg->origin = strdup(buffer);
185    if (!cfg->origin) {
186      DBG("Unable to allocate memory");
187      goto done;
188    } else {
189      should_free_origin = 1;
190    }
191  }
192
193  if (!cfg->appid) {
194    DBG("Appid not specified, using the same value of origin (%s)",
195        cfg->origin);
196    cfg->appid = strdup(cfg->origin);
197    if (!cfg->appid) {
198      DBG("Unable to allocate memory")
199      goto done;
200    } else {
201      should_free_appid = 1;
202    }
203  }
204
205  if (cfg->max_devs == 0) {
206    DBG("Maximum devices number not set. Using default (%d)", MAX_DEVS);
207    cfg->max_devs = MAX_DEVS;
208  }
209
210  devices = calloc(cfg->max_devs, sizeof(device_t));
211  if (!devices) {
212    DBG("Unable to allocate memory");
213    retval = PAM_IGNORE;
214    goto done;
215  }
216
217  pgu_ret = pam_get_user(pamh, &user, NULL);
218  if (pgu_ret != PAM_SUCCESS || user == NULL) {
219    DBG("Unable to access user %s", user);
220    retval = PAM_CONV_ERR;
221    goto done;
222  }
223
224  DBG("Requesting authentication for user %s", user);
225
226  gpn_ret = getpwnam_r(user, &pw_s, buffer, sizeof(buffer), &pw);
227  if (gpn_ret != 0 || pw == NULL || pw->pw_dir == NULL ||
228      pw->pw_dir[0] != '/') {
229    DBG("Unable to retrieve credentials for user %s, (%s)", user,
230        strerror(errno));
231    retval = PAM_USER_UNKNOWN;
232    goto done;
233  }
234
235  DBG("Found user %s", user);
236  DBG("Home directory for %s is %s", user, pw->pw_dir);
237
238  if (!cfg->auth_file) {
239    buf = NULL;
240    authfile_dir = secure_getenv(DEFAULT_AUTHFILE_DIR_VAR);
241    if (!authfile_dir) {
242      DBG("Variable %s is not set. Using default value ($HOME/.config/)",
243          DEFAULT_AUTHFILE_DIR_VAR);
244      authfile_dir_len =
245        strlen(pw->pw_dir) + strlen("/.config") + strlen(DEFAULT_AUTHFILE) + 1;
246      buf = malloc(sizeof(char) * (authfile_dir_len));
247
248      if (!buf) {
249        DBG("Unable to allocate memory");
250        retval = PAM_IGNORE;
251        goto done;
252      }
253
254      /* Opening a file in a users $HOME, need to drop privs for security */
255      openasuser = geteuid() == 0 ? 1 : 0;
256
257      snprintf(buf, authfile_dir_len, "%s/.config%s", pw->pw_dir,
258               DEFAULT_AUTHFILE);
259    } else {
260      DBG("Variable %s set to %s", DEFAULT_AUTHFILE_DIR_VAR, authfile_dir);
261      authfile_dir_len = strlen(authfile_dir) + strlen(DEFAULT_AUTHFILE) + 1;
262      buf = malloc(sizeof(char) * (authfile_dir_len));
263
264      if (!buf) {
265        DBG("Unable to allocate memory");
266        retval = PAM_IGNORE;
267        goto done;
268      }
269
270      snprintf(buf, authfile_dir_len, "%s%s", authfile_dir, DEFAULT_AUTHFILE);
271
272      if (!cfg->openasuser) {
273        DBG("WARNING: not dropping privileges when reading %s, please "
274            "consider setting openasuser=1 in the module configuration",
275            buf);
276      }
277    }
278
279    DBG("Using authentication file %s", buf);
280
281    cfg->auth_file = buf; /* cfg takes ownership */
282    should_free_auth_file = 1;
283    buf = NULL;
284  } else {
285    if (cfg->auth_file[0] != '/') {
286      /* Individual authorization mapping by user: auth_file is not
287          absolute path, so prepend user home dir. */
288      openasuser = geteuid() == 0 ? 1 : 0;
289
290      authfile_dir_len =
291        strlen(pw->pw_dir) + strlen("/") + strlen(cfg->auth_file) + 1;
292      buf = malloc(sizeof(char) * (authfile_dir_len));
293
294      if (!buf) {
295        DBG("Unable to allocate memory");
296        retval = PAM_IGNORE;
297        goto done;
298      }
299
300      snprintf(buf, authfile_dir_len, "%s/%s", pw->pw_dir, cfg->auth_file);
301
302      cfg->auth_file = buf; /* update cfg */
303      should_free_auth_file = 1;
304      buf = NULL;
305    }
306
307    DBG("Using authentication file %s", cfg->auth_file);
308  }
309
310  if (!openasuser) {
311    openasuser = geteuid() == 0 && cfg->openasuser;
312  }
313  if (openasuser) {
314    DBG("Dropping privileges");
315    if (pam_modutil_drop_priv(pamh, &privs, pw)) {
316      DBG("Unable to switch user to uid %i", pw->pw_uid);
317      retval = PAM_IGNORE;
318      goto done;
319    }
320    DBG("Switched to uid %i", pw->pw_uid);
321  }
322  retval =
323    get_devices_from_authfile(cfg->auth_file, user, cfg->max_devs, cfg->debug,
324                              cfg->debug_file, devices, &n_devices);
325  if (openasuser) {
326    if (pam_modutil_regain_priv(pamh, &privs)) {
327      DBG("could not restore privileges");
328      retval = PAM_IGNORE;
329      goto done;
330    }
331    DBG("Restored privileges");
332  }
333
334  if (retval != 1) {
335    // for nouserok; make sure errors in get_devices_from_authfile don't
336    // result in valid devices
337    n_devices = 0;
338  }
339
340  if (n_devices == 0) {
341    if (cfg->nouserok) {
342      DBG("Found no devices but nouserok specified. Skipping authentication");
343      retval = PAM_SUCCESS;
344      goto done;
345    } else if (retval != 1) {
346      DBG("Unable to get devices from file %s", cfg->auth_file);
347      retval = PAM_AUTHINFO_UNAVAIL;
348      goto done;
349    } else {
350      DBG("Found no devices. Aborting.");
351      retval = PAM_AUTHINFO_UNAVAIL;
352      goto done;
353    }
354  }
355
356  // Determine the full path for authpending_file in order to emit touch request
357  // notifications
358  if (!cfg->authpending_file) {
359    int actual_size =
360      snprintf(buffer, BUFSIZE, DEFAULT_AUTHPENDING_FILE_PATH, getuid());
361    if (actual_size >= 0 && actual_size < BUFSIZE) {
362      cfg->authpending_file = strdup(buffer);
363    }
364    if (!cfg->authpending_file) {
365      DBG("Unable to allocate memory for the authpending_file, touch request "
366          "notifications will not be emitted");
367    } else {
368      should_free_authpending_file = 1;
369    }
370  } else {
371    if (strlen(cfg->authpending_file) == 0) {
372      DBG("authpending_file is set to an empty value, touch request "
373          "notifications will be disabled");
374      cfg->authpending_file = NULL;
375    }
376  }
377
378  int authpending_file_descriptor = -1;
379  if (cfg->authpending_file) {
380    DBG("Using file '%s' for emitting touch request notifications",
381        cfg->authpending_file);
382
383    // Open (or create) the authpending_file to indicate that we start waiting
384    // for a touch
385    authpending_file_descriptor =
386      open(cfg->authpending_file,
387           O_RDONLY | O_CREAT | O_CLOEXEC | O_NOFOLLOW | O_NOCTTY, 0664);
388    if (authpending_file_descriptor < 0) {
389      DBG("Unable to emit 'authentication started' notification by opening the "
390          "file '%s', (%s)",
391          cfg->authpending_file, strerror(errno));
392    }
393  }
394
395  if (cfg->manual == 0) {
396    if (cfg->interactive) {
397      converse(pamh, PAM_PROMPT_ECHO_ON,
398               cfg->prompt != NULL ? cfg->prompt : DEFAULT_PROMPT);
399    }
400
401    retval = do_authentication(cfg, devices, n_devices, pamh);
402  } else {
403    retval = do_manual_authentication(cfg, devices, n_devices, pamh);
404  }
405
406  // Close the authpending_file to indicate that we stop waiting for a touch
407  if (authpending_file_descriptor >= 0) {
408    if (close(authpending_file_descriptor) < 0) {
409      DBG("Unable to emit 'authentication stopped' notification by closing the "
410          "file '%s', (%s)",
411          cfg->authpending_file, strerror(errno));
412    }
413  }
414
415  if (retval != 1) {
416    DBG("do_authentication returned %d", retval);
417    retval = PAM_AUTH_ERR;
418    goto done;
419  }
420
421  retval = PAM_SUCCESS;
422
423done:
424  free_devices(devices, n_devices);
425
426  if (buf) {
427    free(buf);
428    buf = NULL;
429  }
430
431  if (should_free_origin) {
432    free((char *) cfg->origin);
433    cfg->origin = NULL;
434  }
435
436  if (should_free_appid) {
437    free((char *) cfg->appid);
438    cfg->appid = NULL;
439  }
440
441  if (should_free_auth_file) {
442    free((char *) cfg->auth_file);
443    cfg->auth_file = NULL;
444  }
445
446  if (should_free_authpending_file) {
447    free((char *) cfg->authpending_file);
448    cfg->authpending_file = NULL;
449  }
450
451  if (cfg->alwaysok && retval != PAM_SUCCESS) {
452    DBG("alwaysok needed (otherwise return with %d)", retval);
453    retval = PAM_SUCCESS;
454  }
455  DBG("done. [%s]", pam_strerror(pamh, retval));
456
457  if (cfg->is_custom_debug_file) {
458    fclose(cfg->debug_file);
459  }
460
461  return retval;
462}
463
464PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc,
465                              const char **argv) {
466  (void) pamh;
467  (void) flags;
468  (void) argc;
469  (void) argv;
470
471  return PAM_SUCCESS;
472}
473