7618d36448cd9267f53b02bcd2f04fe2b3b0e1ef
[dragonfly.git] / lib / libpam / modules / pam_ssh / pam_ssh.c
1 /*-
2  * SPDX-License-Identifier: BSD-3-Clause
3  *
4  * Copyright (c) 2003 Networks Associates Technology, Inc.
5  * Copyright (c) 2004-2011 Dag-Erling Smørgrav
6  * All rights reserved.
7  *
8  * This software was developed for the FreeBSD Project by ThinkSec AS and
9  * NAI Labs, the Security Research Division of Network Associates, Inc.
10  * under DARPA/SPAWAR contract N66001-01-C-8035 ("CBOSS"), as part of the
11  * DARPA CHATS research program.
12  *
13  * Redistribution and use in source and binary forms, with or without
14  * modification, are permitted provided that the following conditions
15  * are met:
16  * 1. Redistributions of source code must retain the above copyright
17  *    notice, this list of conditions and the following disclaimer.
18  * 2. Redistributions in binary form must reproduce the above copyright
19  *    notice, this list of conditions and the following disclaimer in the
20  *    documentation and/or other materials provided with the distribution.
21  * 3. The name of the author may not be used to endorse or promote
22  *    products derived from this software without specific prior written
23  *    permission.
24  *
25  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
26  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
27  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
28  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
29  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
30  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
31  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
32  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
33  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
34  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
35  * SUCH DAMAGE.
36  *
37  * $FreeBSD: head/lib/libpam/modules/pam_ssh/pam_ssh.c 333490 2018-05-11 13:22:43Z des $
38  */
39
40 #include <sys/param.h>
41 #include <sys/wait.h>
42
43 #include <errno.h>
44 #include <fcntl.h>
45 #include <paths.h>
46 #include <pwd.h>
47 #include <signal.h>
48 #include <stdio.h>
49 #include <string.h>
50 #include <unistd.h>
51
52 #define PAM_SM_AUTH
53 #define PAM_SM_SESSION
54
55 #include <security/pam_appl.h>
56 #include <security/pam_modules.h>
57 #include <security/openpam.h>
58
59 #include <openssl/evp.h>
60
61 #define __bounded__(x, y, z)
62 #include "key.h"
63 #include "buffer.h"
64 #include "authfd.h"
65 #include "authfile.h"
66
67 #define ssh_add_identity(auth, key, comment) \
68         ssh_add_identity_constrained(auth, key, comment, 0, 0)
69
70 extern char **environ;
71
72 struct pam_ssh_key {
73         Key     *key;
74         char    *comment;
75 };
76
77 static const char *pam_ssh_prompt = "SSH passphrase: ";
78 static const char *pam_ssh_have_keys = "pam_ssh_have_keys";
79
80 static const char *pam_ssh_keyfiles[] = {
81         ".ssh/identity",        /* SSH1 RSA key */
82         ".ssh/id_rsa",          /* SSH2 RSA key */
83         ".ssh/id_dsa",          /* SSH2 DSA key */
84         ".ssh/id_ecdsa",        /* SSH2 ECDSA key */
85         ".ssh/id_ed25519",      /* SSH2 Ed25519 key */
86         NULL
87 };
88
89 static const char *pam_ssh_agent = "/usr/bin/ssh-agent";
90 static char str_ssh_agent[] = "ssh-agent";
91 static char str_dash_s[] = "-s";
92 static char *const pam_ssh_agent_argv[] = { str_ssh_agent, str_dash_s, NULL };
93 static char *const pam_ssh_agent_envp[] = { NULL };
94
95 /*
96  * Attempts to load a private key from the specified file in the specified
97  * directory, using the specified passphrase.  If successful, returns a
98  * struct pam_ssh_key containing the key and its comment.
99  */
100 static struct pam_ssh_key *
101 pam_ssh_load_key(const char *dir, const char *kfn, const char *passphrase,
102     int nullok)
103 {
104         struct pam_ssh_key *psk;
105         char fn[PATH_MAX];
106         char *comment;
107         Key *key;
108
109         if (snprintf(fn, sizeof(fn), "%s/%s", dir, kfn) > (int)sizeof(fn))
110                 return (NULL);
111         comment = NULL;
112         /*
113          * If the key is unencrypted, OpenSSL ignores the passphrase, so
114          * it will seem like the user typed in the right one.  This allows
115          * a user to circumvent nullok by providing a dummy passphrase.
116          * Verify that the key really *is* encrypted by trying to load it
117          * with an empty passphrase, and if the key is not encrypted,
118          * accept only an empty passphrase.
119          */
120         key = key_load_private(fn, "", &comment);
121         if (key != NULL && !(*passphrase == '\0' && nullok)) {
122                 key_free(key);
123                 return (NULL);
124         }
125         if (key == NULL)
126                 key = key_load_private(fn, passphrase, &comment);
127         if (key == NULL) {
128                 openpam_log(PAM_LOG_DEBUG, "failed to load key from %s", fn);
129                 return (NULL);
130         }
131
132         openpam_log(PAM_LOG_DEBUG, "loaded '%s' from %s", comment, fn);
133         if ((psk = malloc(sizeof(*psk))) == NULL) {
134                 key_free(key);
135                 free(comment);
136                 return (NULL);
137         }
138         psk->key = key;
139         psk->comment = comment;
140         return (psk);
141 }
142
143 /*
144  * Wipes a private key and frees the associated resources.
145  */
146 static void
147 pam_ssh_free_key(pam_handle_t *pamh __unused,
148     void *data, int pam_err __unused)
149 {
150         struct pam_ssh_key *psk;
151
152         psk = data;
153         key_free(psk->key);
154         free(psk->comment);
155         free(psk);
156 }
157
158 PAM_EXTERN int
159 pam_sm_authenticate(pam_handle_t *pamh, int flags __unused,
160     int argc __unused, const char *argv[] __unused)
161 {
162         const char **kfn, *passphrase, *user;
163         const void *item;
164         struct passwd *pwd;
165         struct pam_ssh_key *psk;
166         int nkeys, nullok, pam_err, pass;
167
168         nullok = (openpam_get_option(pamh, "nullok") != NULL);
169
170         /* PEM is not loaded by default */
171         OpenSSL_add_all_algorithms();
172
173         /* get user name and home directory */
174         pam_err = pam_get_user(pamh, &user, NULL);
175         if (pam_err != PAM_SUCCESS)
176                 return (pam_err);
177         pwd = getpwnam(user);
178         if (pwd == NULL)
179                 return (PAM_USER_UNKNOWN);
180         if (pwd->pw_dir == NULL)
181                 return (PAM_AUTH_ERR);
182
183         nkeys = 0;
184         pass = (pam_get_item(pamh, PAM_AUTHTOK, &item) == PAM_SUCCESS &&
185             item != NULL);
186  load_keys:
187         /* get passphrase */
188         pam_err = pam_get_authtok(pamh, PAM_AUTHTOK,
189             &passphrase, pam_ssh_prompt);
190         if (pam_err != PAM_SUCCESS)
191                 return (pam_err);
192
193         /* switch to user credentials */
194         pam_err = openpam_borrow_cred(pamh, pwd);
195         if (pam_err != PAM_SUCCESS)
196                 return (pam_err);
197
198         /* try to load keys from all keyfiles we know of */
199         for (kfn = pam_ssh_keyfiles; *kfn != NULL; ++kfn) {
200                 psk = pam_ssh_load_key(pwd->pw_dir, *kfn, passphrase, nullok);
201                 if (psk != NULL) {
202                         pam_set_data(pamh, *kfn, psk, pam_ssh_free_key);
203                         ++nkeys;
204                 }
205         }
206
207         /* switch back to arbitrator credentials */
208         openpam_restore_cred(pamh);
209
210         /*
211          * If we tried an old token and didn't get anything, and
212          * try_first_pass was specified, try again after prompting the
213          * user for a new passphrase.
214          */
215         if (nkeys == 0 && pass == 1 &&
216             openpam_get_option(pamh, "try_first_pass") != NULL) {
217                 pam_set_item(pamh, PAM_AUTHTOK, NULL);
218                 pass = 0;
219                 goto load_keys;
220         }
221
222         /* no keys? */
223         if (nkeys == 0)
224                 return (PAM_AUTH_ERR);
225
226         pam_set_data(pamh, pam_ssh_have_keys, NULL, NULL);
227         return (PAM_SUCCESS);
228 }
229
230 PAM_EXTERN int
231 pam_sm_setcred(pam_handle_t *pamh __unused, int flags __unused,
232     int argc __unused, const char *argv[] __unused)
233 {
234
235         return (PAM_SUCCESS);
236 }
237
238 /*
239  * Parses a line from ssh-agent's output.
240  */
241 static void
242 pam_ssh_process_agent_output(pam_handle_t *pamh, FILE *f)
243 {
244         char *line, *p, *key, *val;
245         size_t len;
246
247         while ((line = fgetln(f, &len)) != NULL) {
248                 if (len < 4 || strncmp(line, "SSH_", 4) != 0)
249                         continue;
250
251                 /* find equal sign at end of key */
252                 for (p = key = line; p < line + len; ++p)
253                         if (*p == '=')
254                                 break;
255                 if (p == line + len || *p != '=')
256                         continue;
257                 *p = '\0';
258
259                 /* find semicolon at end of value */
260                 for (val = ++p; p < line + len; ++p)
261                         if (*p == ';')
262                                 break;
263                 if (p == line + len || *p != ';')
264                         continue;
265                 *p = '\0';
266
267                 /* store key-value pair in environment */
268                 openpam_log(PAM_LOG_DEBUG, "got %s: %s", key, val);
269                 pam_setenv(pamh, key, val, 1);
270         }
271 }
272
273 /*
274  * Starts an ssh agent and stores the environment variables derived from
275  * its output.
276  */
277 static int
278 pam_ssh_start_agent(pam_handle_t *pamh)
279 {
280         int agent_pipe[2];
281         pid_t pid;
282         FILE *f;
283
284         /* get a pipe which we will use to read the agent's output */
285         if (pipe(agent_pipe) == -1)
286                 return (PAM_SYSTEM_ERR);
287
288         /* start the agent */
289         openpam_log(PAM_LOG_DEBUG, "starting an ssh agent");
290         pid = fork();
291         if (pid == (pid_t)-1) {
292                 /* failed */
293                 close(agent_pipe[0]);
294                 close(agent_pipe[1]);
295                 return (PAM_SYSTEM_ERR);
296         }
297         if (pid == 0) {
298                 int fd;
299
300                 /* child: drop privs, close fds and start agent */
301                 setgid(getegid());
302                 setuid(geteuid());
303                 close(STDIN_FILENO);
304                 open(_PATH_DEVNULL, O_RDONLY);
305                 dup2(agent_pipe[1], STDOUT_FILENO);
306                 dup2(agent_pipe[1], STDERR_FILENO);
307                 for (fd = 3; fd < getdtablesize(); ++fd)
308                         close(fd);
309                 execve(pam_ssh_agent, pam_ssh_agent_argv, pam_ssh_agent_envp);
310                 _exit(127);
311         }
312
313         /* parent */
314         close(agent_pipe[1]);
315         if ((f = fdopen(agent_pipe[0], "r")) == NULL)
316                 return (PAM_SYSTEM_ERR);
317         pam_ssh_process_agent_output(pamh, f);
318         fclose(f);
319
320         return (PAM_SUCCESS);
321 }
322
323 /*
324  * Adds previously stored keys to a running agent.
325  */
326 static int
327 pam_ssh_add_keys_to_agent(pam_handle_t *pamh)
328 {
329         const struct pam_ssh_key *psk;
330         const char **kfn;
331         const void *item;
332         char **envlist, **env;
333         int fd, pam_err;
334
335         /* switch to PAM environment */
336         envlist = environ;
337         if ((environ = pam_getenvlist(pamh)) == NULL) {
338                 environ = envlist;
339                 return (PAM_SYSTEM_ERR);
340         }
341
342         /* get a connection to the agent */
343         if (ssh_get_authentication_socket(&fd) != 0) {
344                 openpam_log(PAM_LOG_DEBUG, "failed to connect to the agent");
345                 pam_err = PAM_SYSTEM_ERR;
346                 goto end;
347         }
348
349         /* look for keys to add to it */
350         for (kfn = pam_ssh_keyfiles; *kfn != NULL; ++kfn) {
351                 pam_err = pam_get_data(pamh, *kfn, &item);
352                 if (pam_err == PAM_SUCCESS && item != NULL) {
353                         psk = item;
354                         if (ssh_add_identity(fd, psk->key, psk->comment) == 0)
355                                 openpam_log(PAM_LOG_DEBUG,
356                                     "added %s to ssh agent", psk->comment);
357                         else
358                                 openpam_log(PAM_LOG_DEBUG, "failed "
359                                     "to add %s to ssh agent", psk->comment);
360                         /* we won't need the key again, so wipe it */
361                         pam_set_data(pamh, *kfn, NULL, NULL);
362                 }
363         }
364         pam_err = PAM_SUCCESS;
365
366         /* disconnect from agent */
367         ssh_close_authentication_socket(fd);
368
369  end:
370         /* switch back to original environment */
371         for (env = environ; *env != NULL; ++env)
372                 free(*env);
373         free(environ);
374         environ = envlist;
375
376         return (pam_err);
377 }
378
379 PAM_EXTERN int
380 pam_sm_open_session(pam_handle_t *pamh, int flags __unused,
381     int argc __unused, const char *argv[] __unused)
382 {
383         struct passwd *pwd;
384         const char *user;
385         const void *data;
386         int pam_err;
387
388         /* no keys, no work */
389         if (pam_get_data(pamh, pam_ssh_have_keys, &data) != PAM_SUCCESS &&
390             openpam_get_option(pamh, "want_agent") == NULL)
391                 return (PAM_SUCCESS);
392
393         /* switch to user credentials */
394         pam_err = pam_get_user(pamh, &user, NULL);
395         if (pam_err != PAM_SUCCESS)
396                 return (pam_err);
397         pwd = getpwnam(user);
398         if (pwd == NULL)
399                 return (PAM_USER_UNKNOWN);
400         pam_err = openpam_borrow_cred(pamh, pwd);
401         if (pam_err != PAM_SUCCESS)
402                 return (pam_err);
403
404         /* start the agent */
405         pam_err = pam_ssh_start_agent(pamh);
406         if (pam_err != PAM_SUCCESS) {
407                 openpam_restore_cred(pamh);
408                 return (pam_err);
409         }
410
411         /* we have an agent, see if we can add any keys to it */
412         pam_err = pam_ssh_add_keys_to_agent(pamh);
413         if (pam_err != PAM_SUCCESS) {
414                 /* XXX ignore failures */
415         }
416
417         openpam_restore_cred(pamh);
418         return (PAM_SUCCESS);
419 }
420
421 PAM_EXTERN int
422 pam_sm_close_session(pam_handle_t *pamh, int flags __unused,
423     int argc __unused, const char *argv[] __unused)
424 {
425         const char *ssh_agent_pid;
426         char *end;
427         int status;
428         pid_t pid;
429
430         if ((ssh_agent_pid = pam_getenv(pamh, "SSH_AGENT_PID")) == NULL) {
431                 openpam_log(PAM_LOG_DEBUG, "no ssh agent");
432                 return (PAM_SUCCESS);
433         }
434         pid = (pid_t)strtol(ssh_agent_pid, &end, 10);
435         if (*ssh_agent_pid == '\0' || *end != '\0') {
436                 openpam_log(PAM_LOG_DEBUG, "invalid ssh agent pid");
437                 return (PAM_SESSION_ERR);
438         }
439         openpam_log(PAM_LOG_DEBUG, "killing ssh agent %d", (int)pid);
440         if (kill(pid, SIGTERM) == -1 ||
441             (waitpid(pid, &status, 0) == -1 && errno != ECHILD))
442                 return (PAM_SYSTEM_ERR);
443         return (PAM_SUCCESS);
444 }
445
446 PAM_MODULE_ENTRY("pam_ssh");