xref: /netbsd-src/external/bsd/pam-u2f/dist/pam-u2f.c (revision 37249b01e651388f226248eb849fe374387a9a0b)
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