Deleted Added
sdiff udiff text old ( 89753 ) new ( 89760 )
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 $");
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
71/*
72 * Generic cleanup function for SSH "Key" type.
73 */
74
75void
76key_cleanup(pam_handle_t *pamh, void *data, int error_status)
77{
78 if (data)
79 key_free(data);
80}
81
82
83/*
84 * Generic PAM cleanup function for this module.
85 */
86
87void
88ssh_cleanup(pam_handle_t *pamh, void *data, int error_status)
89{
90 if (data)
91 free(data);
92}
93
94
95/*
96 * Authenticate a user's key by trying to decrypt it with the password
97 * provided. The key and its comment are then stored for later
98 * retrieval by the session phase. An increasing index is embedded in
99 * the PAM variable names so this function may be called multiple times
100 * for multiple keys.
101 */
102
103int
104auth_via_key(pam_handle_t *pamh, int type, const char *file,
105 const char *dir, const struct passwd *user, const char *pass)
106{
107 char *comment; /* private key comment */
108 char *data_name; /* PAM state */
109 static int index = 0; /* for saved keys */
110 Key *key; /* user's key */
111 char *path; /* to key files */
112 int retval; /* from calls */
113 uid_t saved_uid; /* caller's uid */
114
115 /* locate the user's private key file */
116 if (!asprintf(&path, "%s/%s", dir, file)) {
117 syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
118 return PAM_SERVICE_ERR;
119 }
120 saved_uid = geteuid();
121 /*
122 * Try to decrypt the private key with the passphrase provided.
123 * If success, the user is authenticated.
124 */
125 seteuid(user->pw_uid);
126 key = key_load_private_type(type, path, pass, &comment);
127 free(path);
128 seteuid(saved_uid);
129 if (key == NULL)
130 return PAM_AUTH_ERR;
131 /*
132 * Save the key and comment to pass to ssh-agent in the session
133 * phase.
134 */
135 if (!asprintf(&data_name, "ssh_private_key_%d", index)) {
136 syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
137 free(comment);
138 return PAM_SERVICE_ERR;
139 }
140 retval = pam_set_data(pamh, data_name, key, key_cleanup);
141 free(data_name);
142 if (retval != PAM_SUCCESS) {
143 key_free(key);
144 free(comment);
145 return retval;
146 }
147 if (!asprintf(&data_name, "ssh_key_comment_%d", index)) {
148 syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
149 free(comment);
150 return PAM_SERVICE_ERR;
151 }
152 retval = pam_set_data(pamh, data_name, comment, ssh_cleanup);
153 free(data_name);
154 if (retval != PAM_SUCCESS) {
155 free(comment);
156 return retval;
157 }
158 ++index;
159 return PAM_SUCCESS;
160}
161
162
163PAM_EXTERN int
164pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv)
165{
166 struct options options; /* module options */
167 int authenticated; /* user authenticated? */
168 char *dotdir; /* .ssh2 dir name */
169 struct dirent *dotdir_ent; /* .ssh2 dir entry */
170 DIR *dotdir_p; /* .ssh2 dir pointer */
171 const char *pass; /* passphrase */
172 struct passwd *pwd; /* user's passwd entry */
173 struct passwd *pwd_keep; /* our own copy */
174 int retval; /* from calls */
175 int pam_auth_dsa; /* Authorised via DSA */
176 int pam_auth_rsa; /* Authorised via RSA */
177 const char *user; /* username */
178
179 pam_std_option(&options, NULL, argc, argv);
180
181 PAM_LOG("Options processed");
182
183 retval = pam_get_user(pamh, &user, NULL);
184 if (retval != PAM_SUCCESS)
185 PAM_RETURN(retval);
186 pwd = getpwnam(user);
187 if (pwd == NULL || pwd->pw_dir == NULL)
188 /* delay? */
189 PAM_RETURN(PAM_AUTH_ERR);
190
191 PAM_LOG("Got user: %s", user);
192
193 /*
194 * Pass prompt message to application and receive
195 * passphrase.
196 */
197 retval = pam_get_pass(pamh, &pass, NEED_PASSPHRASE, &options);
198 if (retval != PAM_SUCCESS)
199 PAM_RETURN(retval);
200 OpenSSL_add_all_algorithms(); /* required for DSA */
201
202 PAM_LOG("Got passphrase");
203
204 /*
205 * Either the DSA or the RSA key will authenticate us, but if
206 * we can decrypt both, we'll do so here so we can cache them in
207 * the session phase.
208 */
209 if (!asprintf(&dotdir, "%s/%s", pwd->pw_dir, SSH_CLIENT_DIR)) {
210 syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
211 PAM_RETURN(PAM_SERVICE_ERR);
212 }
213 pam_auth_dsa = auth_via_key(pamh, KEY_DSA, SSH_CLIENT_ID_DSA, dotdir,
214 pwd, pass);
215 pam_auth_rsa = auth_via_key(pamh, KEY_RSA1, SSH_CLIENT_IDENTITY, dotdir,
216 pwd, pass);
217 authenticated = 0;
218 if (pam_auth_dsa == PAM_SUCCESS)
219 authenticated++;
220 if (pam_auth_rsa == PAM_SUCCESS)
221 authenticated++;
222
223 PAM_LOG("Done pre-authenticating; got %d", authenticated);
224
225 /*
226 * Compatibility with SSH2 from SSH Communications Security.
227 */
228 if (!asprintf(&dotdir, "%s/%s", pwd->pw_dir, SSH2_CLIENT_DIR)) {
229 syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
230 PAM_RETURN(PAM_SERVICE_ERR);
231 }
232 /*
233 * Try to load anything that looks like a private key. For
234 * now, we only support DSA and RSA keys.
235 */
236 dotdir_p = opendir(dotdir);
237 while (dotdir_p && (dotdir_ent = readdir(dotdir_p))) {
238 /* skip public keys */
239 if (strcmp(&dotdir_ent->d_name[dotdir_ent->d_namlen -
240 strlen(SSH2_PUB_SUFFIX)], SSH2_PUB_SUFFIX) == 0)
241 continue;
242 /* DSA keys */
243 if (strncmp(dotdir_ent->d_name, SSH2_DSA_PREFIX,
244 strlen(SSH2_DSA_PREFIX)) == 0)
245 retval = auth_via_key(pamh, KEY_DSA,
246 dotdir_ent->d_name, dotdir, pwd, pass);
247 /* RSA keys */
248 else if (strncmp(dotdir_ent->d_name, SSH2_RSA_PREFIX,
249 strlen(SSH2_RSA_PREFIX)) == 0)
250 retval = auth_via_key(pamh, KEY_RSA,
251 dotdir_ent->d_name, dotdir, pwd, pass);
252 /* skip other files */
253 else
254 continue;
255 authenticated += (retval == PAM_SUCCESS);
256 }
257 if (!authenticated) {
258 PAM_VERBOSE_ERROR("SSH authentication refused");
259 PAM_RETURN(PAM_AUTH_ERR);
260 }
261
262 PAM_LOG("Done authenticating; got %d", authenticated);
263
264 /*
265 * Copy the passwd entry (in case successive calls are made)
266 * and save it for the session phase.
267 */
268 pwd_keep = malloc(sizeof *pwd);
269 if (pwd_keep == NULL) {
270 syslog(LOG_CRIT, "%m");
271 PAM_RETURN(PAM_SERVICE_ERR);
272 }
273 memcpy(pwd_keep, pwd, sizeof *pwd_keep);
274 retval = pam_set_data(pamh, "ssh_passwd_entry", pwd_keep, ssh_cleanup);
275 if (retval != PAM_SUCCESS) {
276 free(pwd_keep);
277 PAM_RETURN(retval);
278 }
279
280 PAM_LOG("Saved ssh_passwd_entry");
281
282 PAM_RETURN(PAM_SUCCESS);
283}
284
285
286PAM_EXTERN int
287pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv)
288{
289 struct options options; /* module options */
290
291 pam_std_option(&options, NULL, argc, argv);
292
293 PAM_LOG("Options processed");
294
295 PAM_RETURN(PAM_SUCCESS);
296}
297
298PAM_EXTERN int
299pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc ,const char **argv)
300{
301 struct options options;
302
303 pam_std_option(&options, NULL, argc, argv);
304
305 PAM_LOG("Options processed");
306
307 PAM_RETURN(PAM_IGNORE);
308}
309
310PAM_EXTERN int
311pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, const char **argv)
312{
313 struct options options;
314
315 pam_std_option(&options, NULL, argc, argv);
316
317 PAM_LOG("Options processed");
318
319 PAM_RETURN(PAM_IGNORE);
320}
321
322typedef AuthenticationConnection AC;
323
324PAM_EXTERN int
325pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, const char **argv)
326{
327 struct options options; /* module options */
328 AC *ac; /* to ssh-agent */
329 char *agent_socket; /* agent socket */
330 char *comment; /* on private key */
331 char *env_end; /* end of env */
332 char *env_file; /* to store env */
333 FILE *env_fp; /* env_file handle */
334 char *env_value; /* envariable value */
335 char *data_name; /* PAM state */
336 int final; /* final return value */
337 int index; /* for saved keys */
338 Key *key; /* user's private key */
339 FILE *pipe; /* ssh-agent handle */
340 struct passwd *pwd; /* user's passwd entry */
341 int retval; /* from calls */
342 uid_t saved_uid; /* caller's uid */
343 const char *tty; /* tty or display name */
344 char hname[MAXHOSTNAMELEN]; /* local hostname */
345 char env_string[BUFSIZ]; /* environment string */
346
347 pam_std_option(&options, NULL, argc, argv);
348
349 PAM_LOG("Options processed");
350
351 /* dump output of ssh-agent in ~/.ssh */
352 retval = pam_get_data(pamh, "ssh_passwd_entry", (const void **)&pwd);
353 if (retval != PAM_SUCCESS)
354 PAM_RETURN(retval);
355
356 PAM_LOG("Got ssh_passwd_entry");
357
358 /* use the tty or X display name in the filename */
359 retval = pam_get_item(pamh, PAM_TTY, (const void **)&tty);
360 if (retval != PAM_SUCCESS)
361 PAM_RETURN(retval);
362
363 PAM_LOG("Got TTY");
364
365 if (gethostname(hname, sizeof hname) == 0) {
366 if (asprintf(&env_file, "%s/.ssh/agent-%s%s%s",
367 pwd->pw_dir, hname, *tty == ':' ? "" : ":", tty)
368 == -1) {
369 syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
370 PAM_RETURN(PAM_SERVICE_ERR);
371 }
372 }
373 else if (asprintf(&env_file, "%s/.ssh/agent-%s", pwd->pw_dir,
374 tty) == -1) {
375 syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
376 PAM_RETURN(PAM_SERVICE_ERR);
377 }
378
379 PAM_LOG("Got env_file: %s", env_file);
380
381 /* save the filename so we can delete the file on session close */
382 retval = pam_set_data(pamh, "ssh_agent_env", env_file, ssh_cleanup);
383 if (retval != PAM_SUCCESS) {
384 free(env_file);
385 PAM_RETURN(retval);
386 }
387
388 PAM_LOG("Saved env_file");
389
390 /* start the agent as the user */
391 saved_uid = geteuid();
392 seteuid(pwd->pw_uid);
393 env_fp = fopen(env_file, "w");
394 if (env_fp != NULL)
395 chmod(env_file, S_IRUSR);
396 pipe = popen(SSH_AGENT, "r");
397 seteuid(saved_uid);
398 if (!pipe) {
399 syslog(LOG_ERR, "%s: %s: %m", MODULE_NAME, SSH_AGENT);
400 if (env_fp)
401 fclose(env_fp);
402 PAM_RETURN(PAM_SESSION_ERR);
403 }
404
405 PAM_LOG("Agent started as user");
406
407 /*
408 * Save environment for application with pam_putenv().
409 */
410 agent_socket = NULL;
411 while (fgets(env_string, sizeof env_string, pipe)) {
412 if (env_fp)
413 fputs(env_string, env_fp);
414 env_value = strchr(env_string, '=');
415 if (env_value == NULL)
416 continue;
417 env_end = strchr(env_value, ';');
418 if (env_end == NULL)
419 continue;
420 *env_end = '\0';
421 /* pass to the application ... */
422 retval = pam_putenv(pamh, env_string);
423 if (retval != PAM_SUCCESS) {
424 pclose(pipe);
425 if (env_fp)
426 fclose(env_fp);
427 PAM_RETURN(PAM_SERVICE_ERR);
428 }
429
430 PAM_LOG("Put to environment: %s", env_string);
431
432 *env_value++ = '\0';
433 if (strcmp(&env_string[strlen(env_string) -
434 strlen(ENV_SOCKET_SUFFIX)], ENV_SOCKET_SUFFIX) == 0) {
435 agent_socket = strdup(env_value);
436 if (agent_socket == NULL) {
437 syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
438 PAM_RETURN(PAM_SERVICE_ERR);
439 }
440 }
441 else if (strcmp(&env_string[strlen(env_string) -
442 strlen(ENV_PID_SUFFIX)], ENV_PID_SUFFIX) == 0) {
443 env_value = strdup(env_value);
444 if (env_value == NULL) {
445 syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
446 PAM_RETURN(PAM_SERVICE_ERR);
447 }
448 retval = pam_set_data(pamh, "ssh_agent_pid",
449 env_value, ssh_cleanup);
450 if (retval != PAM_SUCCESS)
451 PAM_RETURN(retval);
452 PAM_LOG("Environment write successful");
453 }
454 }
455 if (env_fp)
456 fclose(env_fp);
457 retval = pclose(pipe);
458 switch (retval) {
459 case -1:
460 syslog(LOG_ERR, "%s: %s: %m", MODULE_NAME, SSH_AGENT);
461 PAM_RETURN(PAM_SESSION_ERR);
462 case 0:
463 break;
464 case 127:
465 syslog(LOG_ERR, "%s: cannot execute %s", MODULE_NAME,
466 SSH_AGENT);
467 PAM_RETURN(PAM_SESSION_ERR);
468 default:
469 syslog(LOG_ERR, "%s: %s exited %s %d", MODULE_NAME,
470 SSH_AGENT, WIFSIGNALED(retval) ? "on signal" :
471 "with status", WIFSIGNALED(retval) ? WTERMSIG(retval) :
472 WEXITSTATUS(retval));
473 PAM_RETURN(PAM_SESSION_ERR);
474 }
475 if (agent_socket == NULL)
476 PAM_RETURN(PAM_SESSION_ERR);
477
478 PAM_LOG("Environment saved");
479
480 /* connect to the agent */
481 ac = ssh_get_authentication_connection();
482 if (!ac) {
483 syslog(LOG_ERR, "%s: %s: %m", MODULE_NAME, agent_socket);
484 PAM_RETURN(PAM_SESSION_ERR);
485 }
486
487 PAM_LOG("Connected to agent");
488
489 /* hand off each private key to the agent */
490 final = 0;
491 for (index = 0; ; index++) {
492 if (!asprintf(&data_name, "ssh_private_key_%d", index)) {
493 syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
494 ssh_close_authentication_connection(ac);
495 PAM_RETURN(PAM_SERVICE_ERR);
496 }
497 retval = pam_get_data(pamh, data_name, (const void **)&key);
498 free(data_name);
499 if (retval != PAM_SUCCESS)
500 break;
501 if (!asprintf(&data_name, "ssh_key_comment_%d", index)) {
502 syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
503 ssh_close_authentication_connection(ac);
504 PAM_RETURN(PAM_SERVICE_ERR);
505 }
506 retval = pam_get_data(pamh, data_name, (const void **)&comment);
507 free(data_name);
508 if (retval != PAM_SUCCESS)
509 break;
510 retval = ssh_add_identity(ac, key, comment);
511 if (!final)
512 final = retval;
513 }
514 ssh_close_authentication_connection(ac);
515
516 PAM_LOG("Keys handed off");
517
518 PAM_RETURN(final ? PAM_SUCCESS : PAM_SESSION_ERR);
519}
520
521
522PAM_EXTERN int
523pam_sm_close_session(pam_handle_t *pamh, int flags, int argc, const char **argv)
524{
525 struct options options; /* module options */
526 const char *env_file; /* ssh-agent environment */
527 pid_t pid; /* ssh-agent process id */
528 int retval; /* from calls */
529 const char *ssh_agent_pid; /* ssh-agent pid string */
530
531 pam_std_option(&options, NULL, argc, argv);
532
533 PAM_LOG("Options processed");
534
535 /* retrieve environment filename, then remove the file */
536 retval = pam_get_data(pamh, "ssh_agent_env", (const void **)&env_file);
537 if (retval != PAM_SUCCESS)
538 PAM_RETURN(retval);
539 unlink(env_file);
540
541 PAM_LOG("Got ssh_agent_env");
542
543 /* retrieve the agent's process id */
544 retval = pam_get_data(pamh, "ssh_agent_pid", (const void **)&ssh_agent_pid);
545 if (retval != PAM_SUCCESS)
546 PAM_RETURN(retval);
547
548 PAM_LOG("Got ssh_agent_pid");
549
550 /*
551 * Kill the agent. SSH2 from SSH Communications Security does
552 * not have a -k option, so we just call kill().
553 */
554 pid = atoi(ssh_agent_pid);
555 if (pid <= 0)
556 PAM_RETURN(PAM_SESSION_ERR);
557 if (kill(pid, SIGTERM) != 0) {
558 syslog(LOG_ERR, "%s: %s: %m", MODULE_NAME, ssh_agent_pid);
559 PAM_RETURN(PAM_SESSION_ERR);
560 }
561
562 PAM_LOG("Agent killed");
563
564 PAM_RETURN(PAM_SUCCESS);
565}
566
567PAM_MODULE_ENTRY("pam_ssh");