Deleted Added
full compact
1/*-
2 * Copyright (c) 1999, 2000 Andrew J. Korty
3 * All rights reserved.
4 * Copyright (c) 2001 Networks Associates Technologies, Inc.
5 * All rights reserved.
6 *
7 * Portions of this software were developed for the FreeBSD Project by
8 * ThinkSec AS and NAI Labs, the Security Research Division of Network
9 * Associates, Inc. under DARPA/SPAWAR contract N66001-01-C-8035
10 * ("CBOSS"), as part of the DARPA CHATS research program.
11 *
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions
14 * are met:
15 * 1. Redistributions of source code must retain the above copyright
16 * notice, this list of conditions and the following disclaimer.
17 * 2. Redistributions in binary form must reproduce the above copyright
18 * notice, this list of conditions and the following disclaimer in the
19 * documentation and/or other materials provided with the distribution.
20 * 3. The name of the author may not be used to endorse or promote
21 * products derived from this software without specific prior written
22 * permission.
23 *
24 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34 * SUCH DAMAGE.
35 */
36
37#include <sys/cdefs.h>
38__FBSDID("$FreeBSD: head/lib/libpam/modules/pam_ssh/pam_ssh.c 89753 2002-01-24 17:26:27Z des $");
38__FBSDID("$FreeBSD: head/lib/libpam/modules/pam_ssh/pam_ssh.c 89760 2002-01-24 18:37:17Z markm $");
39
40#include <sys/param.h>
41#include <sys/socket.h>
42#include <sys/stat.h>
43#include <sys/wait.h>
44
45#include <dirent.h>
46#include <pwd.h>
47#include <signal.h>
48#include <ssh.h>
49#include <stdio.h>
50#include <stdlib.h>
51#include <string.h>
52#include <unistd.h>
53
54#define PAM_SM_AUTH
55#define PAM_SM_ACCOUNT
56#define PAM_SM_SESSION
57#define PAM_SM_PASSWORD
58
59#include <security/pam_modules.h>
60#include <security/pam_mod_misc.h>
61
62#include <openssl/dsa.h>
63#include <openssl/evp.h>
64
65#include "key.h"
66#include "authfd.h"
67#include "authfile.h"
68#include "log.h"
69#include "pam_ssh.h"
70
71static int auth_via_key(pam_handle_t *, int, const char *, const char *, const struct passwd *, const char *);
72static void key_cleanup(pam_handle_t *, void *, int);
73static void ssh_cleanup(pam_handle_t *, void *, int);
74
75/*
76 * Generic cleanup function for SSH "Key" type.
77 */
78
75void
76key_cleanup(pam_handle_t *pamh, void *data, int error_status)
79static void
80key_cleanup(pam_handle_t *pamh __unused, void *data, int error_status __unused)
81{
82 if (data)
83 key_free(data);
84}
85
86
87/*
88 * Generic PAM cleanup function for this module.
89 */
90
87void
88ssh_cleanup(pam_handle_t *pamh, void *data, int error_status)
91static void
92ssh_cleanup(pam_handle_t *pamh __unused, void *data, int error_status __unused)
93{
94 if (data)
95 free(data);
96}
97
98
99/*
100 * Authenticate a user's key by trying to decrypt it with the password
101 * provided. The key and its comment are then stored for later
102 * retrieval by the session phase. An increasing index is embedded in
103 * the PAM variable names so this function may be called multiple times
104 * for multiple keys.
105 */
106
103int
107static int
108auth_via_key(pam_handle_t *pamh, int type, const char *file,
109 const char *dir, const struct passwd *user, const char *pass)
110{
111 char *comment; /* private key comment */
112 char *data_name; /* PAM state */
109 static int index = 0; /* for saved keys */
113 static int indx = 0; /* for saved keys */
114 Key *key; /* user's key */
115 char *path; /* to key files */
116 int retval; /* from calls */
117 uid_t saved_uid; /* caller's uid */
118
119 /* locate the user's private key file */
120 if (!asprintf(&path, "%s/%s", dir, file)) {
121 syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
122 return PAM_SERVICE_ERR;
123 }
124 saved_uid = geteuid();
125 /*
126 * Try to decrypt the private key with the passphrase provided.
127 * If success, the user is authenticated.
128 */
129 seteuid(user->pw_uid);
130 key = key_load_private_type(type, path, pass, &comment);
131 free(path);
132 seteuid(saved_uid);
133 if (key == NULL)
134 return PAM_AUTH_ERR;
135 /*
136 * Save the key and comment to pass to ssh-agent in the session
137 * phase.
138 */
135 if (!asprintf(&data_name, "ssh_private_key_%d", index)) {
139 if (!asprintf(&data_name, "ssh_private_key_%d", indx)) {
140 syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
141 free(comment);
142 return PAM_SERVICE_ERR;
143 }
144 retval = pam_set_data(pamh, data_name, key, key_cleanup);
145 free(data_name);
146 if (retval != PAM_SUCCESS) {
147 key_free(key);
148 free(comment);
149 return retval;
150 }
147 if (!asprintf(&data_name, "ssh_key_comment_%d", index)) {
151 if (!asprintf(&data_name, "ssh_key_comment_%d", indx)) {
152 syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
153 free(comment);
154 return PAM_SERVICE_ERR;
155 }
156 retval = pam_set_data(pamh, data_name, comment, ssh_cleanup);
157 free(data_name);
158 if (retval != PAM_SUCCESS) {
159 free(comment);
160 return retval;
161 }
158 ++index;
162 ++indx;
163 return PAM_SUCCESS;
164}
165
166
167PAM_EXTERN int
164pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv)
168pam_sm_authenticate(pam_handle_t *pamh, int flags __unused, int argc, const char **argv)
169{
170 struct options options; /* module options */
171 int authenticated; /* user authenticated? */
172 char *dotdir; /* .ssh2 dir name */
173 struct dirent *dotdir_ent; /* .ssh2 dir entry */
174 DIR *dotdir_p; /* .ssh2 dir pointer */
175 const char *pass; /* passphrase */
176 struct passwd *pwd; /* user's passwd entry */
177 struct passwd *pwd_keep; /* our own copy */
178 int retval; /* from calls */
179 int pam_auth_dsa; /* Authorised via DSA */
180 int pam_auth_rsa; /* Authorised via RSA */
181 const char *user; /* username */
182
183 pam_std_option(&options, NULL, argc, argv);
184
185 PAM_LOG("Options processed");
186
187 retval = pam_get_user(pamh, &user, NULL);
188 if (retval != PAM_SUCCESS)
189 PAM_RETURN(retval);
190 pwd = getpwnam(user);
191 if (pwd == NULL || pwd->pw_dir == NULL)
192 /* delay? */
193 PAM_RETURN(PAM_AUTH_ERR);
194
195 PAM_LOG("Got user: %s", user);
196
197 /*
198 * Pass prompt message to application and receive
199 * passphrase.
200 */
201 retval = pam_get_pass(pamh, &pass, NEED_PASSPHRASE, &options);
202 if (retval != PAM_SUCCESS)
203 PAM_RETURN(retval);
204 OpenSSL_add_all_algorithms(); /* required for DSA */
205
206 PAM_LOG("Got passphrase");
207
208 /*
209 * Either the DSA or the RSA key will authenticate us, but if
210 * we can decrypt both, we'll do so here so we can cache them in
211 * the session phase.
212 */
213 if (!asprintf(&dotdir, "%s/%s", pwd->pw_dir, SSH_CLIENT_DIR)) {
214 syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
215 PAM_RETURN(PAM_SERVICE_ERR);
216 }
217 pam_auth_dsa = auth_via_key(pamh, KEY_DSA, SSH_CLIENT_ID_DSA, dotdir,
218 pwd, pass);
219 pam_auth_rsa = auth_via_key(pamh, KEY_RSA1, SSH_CLIENT_IDENTITY, dotdir,
220 pwd, pass);
221 authenticated = 0;
222 if (pam_auth_dsa == PAM_SUCCESS)
223 authenticated++;
224 if (pam_auth_rsa == PAM_SUCCESS)
225 authenticated++;
226
227 PAM_LOG("Done pre-authenticating; got %d", authenticated);
228
229 /*
230 * Compatibility with SSH2 from SSH Communications Security.
231 */
232 if (!asprintf(&dotdir, "%s/%s", pwd->pw_dir, SSH2_CLIENT_DIR)) {
233 syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
234 PAM_RETURN(PAM_SERVICE_ERR);
235 }
236 /*
237 * Try to load anything that looks like a private key. For
238 * now, we only support DSA and RSA keys.
239 */
240 dotdir_p = opendir(dotdir);
241 while (dotdir_p && (dotdir_ent = readdir(dotdir_p))) {
242 /* skip public keys */
243 if (strcmp(&dotdir_ent->d_name[dotdir_ent->d_namlen -
244 strlen(SSH2_PUB_SUFFIX)], SSH2_PUB_SUFFIX) == 0)
245 continue;
246 /* DSA keys */
247 if (strncmp(dotdir_ent->d_name, SSH2_DSA_PREFIX,
248 strlen(SSH2_DSA_PREFIX)) == 0)
249 retval = auth_via_key(pamh, KEY_DSA,
250 dotdir_ent->d_name, dotdir, pwd, pass);
251 /* RSA keys */
252 else if (strncmp(dotdir_ent->d_name, SSH2_RSA_PREFIX,
253 strlen(SSH2_RSA_PREFIX)) == 0)
254 retval = auth_via_key(pamh, KEY_RSA,
255 dotdir_ent->d_name, dotdir, pwd, pass);
256 /* skip other files */
257 else
258 continue;
259 authenticated += (retval == PAM_SUCCESS);
260 }
261 if (!authenticated) {
262 PAM_VERBOSE_ERROR("SSH authentication refused");
263 PAM_RETURN(PAM_AUTH_ERR);
264 }
265
266 PAM_LOG("Done authenticating; got %d", authenticated);
267
268 /*
269 * Copy the passwd entry (in case successive calls are made)
270 * and save it for the session phase.
271 */
272 pwd_keep = malloc(sizeof *pwd);
273 if (pwd_keep == NULL) {
274 syslog(LOG_CRIT, "%m");
275 PAM_RETURN(PAM_SERVICE_ERR);
276 }
277 memcpy(pwd_keep, pwd, sizeof *pwd_keep);
278 retval = pam_set_data(pamh, "ssh_passwd_entry", pwd_keep, ssh_cleanup);
279 if (retval != PAM_SUCCESS) {
280 free(pwd_keep);
281 PAM_RETURN(retval);
282 }
283
284 PAM_LOG("Saved ssh_passwd_entry");
285
286 PAM_RETURN(PAM_SUCCESS);
287}
288
289
290PAM_EXTERN int
287pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv)
291pam_sm_setcred(pam_handle_t *pamh __unused, int flags __unused, int argc, const char **argv)
292{
293 struct options options; /* module options */
294
295 pam_std_option(&options, NULL, argc, argv);
296
297 PAM_LOG("Options processed");
298
299 PAM_RETURN(PAM_SUCCESS);
300}
301
302PAM_EXTERN int
299pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc ,const char **argv)
303pam_sm_acct_mgmt(pam_handle_t *pamh __unused, int flags __unused, int argc ,const char **argv)
304{
305 struct options options;
306
307 pam_std_option(&options, NULL, argc, argv);
308
309 PAM_LOG("Options processed");
310
311 PAM_RETURN(PAM_IGNORE);
312}
313
314PAM_EXTERN int
311pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, const char **argv)
315pam_sm_chauthtok(pam_handle_t *pamh __unused, int flags __unused, int argc, const char **argv)
316{
317 struct options options;
318
319 pam_std_option(&options, NULL, argc, argv);
320
321 PAM_LOG("Options processed");
322
323 PAM_RETURN(PAM_IGNORE);
324}
325
326typedef AuthenticationConnection AC;
327
328PAM_EXTERN int
325pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, const char **argv)
329pam_sm_open_session(pam_handle_t *pamh, int flags __unused, int argc, const char **argv)
330{
331 struct options options; /* module options */
332 AC *ac; /* to ssh-agent */
333 char *agent_socket; /* agent socket */
334 char *comment; /* on private key */
335 char *env_end; /* end of env */
336 char *env_file; /* to store env */
337 FILE *env_fp; /* env_file handle */
338 char *env_value; /* envariable value */
339 char *data_name; /* PAM state */
340 int final; /* final return value */
337 int index; /* for saved keys */
341 int indx; /* for saved keys */
342 Key *key; /* user's private key */
339 FILE *pipe; /* ssh-agent handle */
343 FILE *lpipe; /* ssh-agent handle */
344 struct passwd *pwd; /* user's passwd entry */
345 int retval; /* from calls */
346 uid_t saved_uid; /* caller's uid */
347 const char *tty; /* tty or display name */
348 char hname[MAXHOSTNAMELEN]; /* local hostname */
349 char env_string[BUFSIZ]; /* environment string */
350
351 pam_std_option(&options, NULL, argc, argv);
352
353 PAM_LOG("Options processed");
354
355 /* dump output of ssh-agent in ~/.ssh */
356 retval = pam_get_data(pamh, "ssh_passwd_entry", (const void **)&pwd);
357 if (retval != PAM_SUCCESS)
358 PAM_RETURN(retval);
359
360 PAM_LOG("Got ssh_passwd_entry");
361
362 /* use the tty or X display name in the filename */
363 retval = pam_get_item(pamh, PAM_TTY, (const void **)&tty);
364 if (retval != PAM_SUCCESS)
365 PAM_RETURN(retval);
366
367 PAM_LOG("Got TTY");
368
369 if (gethostname(hname, sizeof hname) == 0) {
370 if (asprintf(&env_file, "%s/.ssh/agent-%s%s%s",
371 pwd->pw_dir, hname, *tty == ':' ? "" : ":", tty)
372 == -1) {
373 syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
374 PAM_RETURN(PAM_SERVICE_ERR);
375 }
376 }
377 else if (asprintf(&env_file, "%s/.ssh/agent-%s", pwd->pw_dir,
378 tty) == -1) {
379 syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
380 PAM_RETURN(PAM_SERVICE_ERR);
381 }
382
383 PAM_LOG("Got env_file: %s", env_file);
384
385 /* save the filename so we can delete the file on session close */
386 retval = pam_set_data(pamh, "ssh_agent_env", env_file, ssh_cleanup);
387 if (retval != PAM_SUCCESS) {
388 free(env_file);
389 PAM_RETURN(retval);
390 }
391
392 PAM_LOG("Saved env_file");
393
394 /* start the agent as the user */
395 saved_uid = geteuid();
396 seteuid(pwd->pw_uid);
397 env_fp = fopen(env_file, "w");
398 if (env_fp != NULL)
399 chmod(env_file, S_IRUSR);
396 pipe = popen(SSH_AGENT, "r");
400 lpipe = popen(SSH_AGENT, "r");
401 seteuid(saved_uid);
398 if (!pipe) {
402 if (!lpipe) {
403 syslog(LOG_ERR, "%s: %s: %m", MODULE_NAME, SSH_AGENT);
404 if (env_fp)
405 fclose(env_fp);
406 PAM_RETURN(PAM_SESSION_ERR);
407 }
408
409 PAM_LOG("Agent started as user");
410
411 /*
412 * Save environment for application with pam_putenv().
413 */
414 agent_socket = NULL;
411 while (fgets(env_string, sizeof env_string, pipe)) {
415 while (fgets(env_string, sizeof env_string, lpipe)) {
416 if (env_fp)
417 fputs(env_string, env_fp);
418 env_value = strchr(env_string, '=');
419 if (env_value == NULL)
420 continue;
421 env_end = strchr(env_value, ';');
422 if (env_end == NULL)
423 continue;
424 *env_end = '\0';
425 /* pass to the application ... */
426 retval = pam_putenv(pamh, env_string);
427 if (retval != PAM_SUCCESS) {
424 pclose(pipe);
428 pclose(lpipe);
429 if (env_fp)
430 fclose(env_fp);
431 PAM_RETURN(PAM_SERVICE_ERR);
432 }
433
434 PAM_LOG("Put to environment: %s", env_string);
435
436 *env_value++ = '\0';
437 if (strcmp(&env_string[strlen(env_string) -
438 strlen(ENV_SOCKET_SUFFIX)], ENV_SOCKET_SUFFIX) == 0) {
439 agent_socket = strdup(env_value);
440 if (agent_socket == NULL) {
441 syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
442 PAM_RETURN(PAM_SERVICE_ERR);
443 }
444 }
445 else if (strcmp(&env_string[strlen(env_string) -
446 strlen(ENV_PID_SUFFIX)], ENV_PID_SUFFIX) == 0) {
447 env_value = strdup(env_value);
448 if (env_value == NULL) {
449 syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
450 PAM_RETURN(PAM_SERVICE_ERR);
451 }
452 retval = pam_set_data(pamh, "ssh_agent_pid",
453 env_value, ssh_cleanup);
454 if (retval != PAM_SUCCESS)
455 PAM_RETURN(retval);
456 PAM_LOG("Environment write successful");
457 }
458 }
459 if (env_fp)
460 fclose(env_fp);
457 retval = pclose(pipe);
461 retval = pclose(lpipe);
462 switch (retval) {
463 case -1:
464 syslog(LOG_ERR, "%s: %s: %m", MODULE_NAME, SSH_AGENT);
465 PAM_RETURN(PAM_SESSION_ERR);
466 case 0:
467 break;
468 case 127:
469 syslog(LOG_ERR, "%s: cannot execute %s", MODULE_NAME,
470 SSH_AGENT);
471 PAM_RETURN(PAM_SESSION_ERR);
472 default:
473 syslog(LOG_ERR, "%s: %s exited %s %d", MODULE_NAME,
474 SSH_AGENT, WIFSIGNALED(retval) ? "on signal" :
475 "with status", WIFSIGNALED(retval) ? WTERMSIG(retval) :
476 WEXITSTATUS(retval));
477 PAM_RETURN(PAM_SESSION_ERR);
478 }
479 if (agent_socket == NULL)
480 PAM_RETURN(PAM_SESSION_ERR);
481
482 PAM_LOG("Environment saved");
483
484 /* connect to the agent */
485 ac = ssh_get_authentication_connection();
486 if (!ac) {
487 syslog(LOG_ERR, "%s: %s: %m", MODULE_NAME, agent_socket);
488 PAM_RETURN(PAM_SESSION_ERR);
489 }
490
491 PAM_LOG("Connected to agent");
492
493 /* hand off each private key to the agent */
494 final = 0;
491 for (index = 0; ; index++) {
492 if (!asprintf(&data_name, "ssh_private_key_%d", index)) {
495 for (indx = 0; ; indx++) {
496 if (!asprintf(&data_name, "ssh_private_key_%d", indx)) {
497 syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
498 ssh_close_authentication_connection(ac);
499 PAM_RETURN(PAM_SERVICE_ERR);
500 }
501 retval = pam_get_data(pamh, data_name, (const void **)&key);
502 free(data_name);
503 if (retval != PAM_SUCCESS)
504 break;
501 if (!asprintf(&data_name, "ssh_key_comment_%d", index)) {
505 if (!asprintf(&data_name, "ssh_key_comment_%d", indx)) {
506 syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
507 ssh_close_authentication_connection(ac);
508 PAM_RETURN(PAM_SERVICE_ERR);
509 }
510 retval = pam_get_data(pamh, data_name, (const void **)&comment);
511 free(data_name);
512 if (retval != PAM_SUCCESS)
513 break;
514 retval = ssh_add_identity(ac, key, comment);
515 if (!final)
516 final = retval;
517 }
518 ssh_close_authentication_connection(ac);
519
520 PAM_LOG("Keys handed off");
521
522 PAM_RETURN(final ? PAM_SUCCESS : PAM_SESSION_ERR);
523}
524
525
526PAM_EXTERN int
523pam_sm_close_session(pam_handle_t *pamh, int flags, int argc, const char **argv)
527pam_sm_close_session(pam_handle_t *pamh, int flags __unused, int argc, const char **argv)
528{
529 struct options options; /* module options */
530 const char *env_file; /* ssh-agent environment */
531 pid_t pid; /* ssh-agent process id */
532 int retval; /* from calls */
533 const char *ssh_agent_pid; /* ssh-agent pid string */
534
535 pam_std_option(&options, NULL, argc, argv);
536
537 PAM_LOG("Options processed");
538
539 /* retrieve environment filename, then remove the file */
540 retval = pam_get_data(pamh, "ssh_agent_env", (const void **)&env_file);
541 if (retval != PAM_SUCCESS)
542 PAM_RETURN(retval);
543 unlink(env_file);
544
545 PAM_LOG("Got ssh_agent_env");
546
547 /* retrieve the agent's process id */
548 retval = pam_get_data(pamh, "ssh_agent_pid", (const void **)&ssh_agent_pid);
549 if (retval != PAM_SUCCESS)
550 PAM_RETURN(retval);
551
552 PAM_LOG("Got ssh_agent_pid");
553
554 /*
555 * Kill the agent. SSH2 from SSH Communications Security does
556 * not have a -k option, so we just call kill().
557 */
558 pid = atoi(ssh_agent_pid);
559 if (pid <= 0)
560 PAM_RETURN(PAM_SESSION_ERR);
561 if (kill(pid, SIGTERM) != 0) {
562 syslog(LOG_ERR, "%s: %s: %m", MODULE_NAME, ssh_agent_pid);
563 PAM_RETURN(PAM_SESSION_ERR);
564 }
565
566 PAM_LOG("Agent killed");
567
568 PAM_RETURN(PAM_SUCCESS);
569}
570
571PAM_MODULE_ENTRY("pam_ssh");