2 * Copyright (c) 1999, 2000 Andrew J. Korty
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 #include <sys/param.h>
32 #include <sys/queue.h>
44 #define PAM_SM_SESSION
45 #include <security/pam_modules.h>
46 #include <security/pam_mod_misc.h>
53 #define MODULE_NAME "pam_ssh"
54 #define NEED_PASSPHRASE "Need passphrase for %s (%s).\nEnter passphrase: "
55 #define PATH_SSH_AGENT "/usr/bin/ssh-agent"
59 rsa_cleanup(pam_handle_t *pamh, void *data, int error_status)
67 ssh_cleanup(pam_handle_t *pamh, void *data, int error_status)
75 * The following set of functions allow the module to manipulate the
76 * environment without calling the putenv() or setenv() stdlib functions.
77 * At least one version of these functions, on the first call, copies
78 * the environment into dynamically-allocated memory and then augments
79 * it. On subsequent calls, the realloc() call is used to grow the
80 * previously allocated buffer. Problems arise when the "environ"
81 * variable is changed to point to static memory after putenv()/setenv()
84 * We don't use putenv() or setenv() in case the application subsequently
85 * manipulates environ, (e.g., to clear the environment by pointing
86 * environ at an array of one element equal to NULL).
89 SLIST_HEAD(env_head, env_entry);
93 SLIST_ENTRY(env_entry) ee_entries;
97 char **e_environ_orig;
100 struct env_head e_head;
104 extern char **environ;
112 if (!(self = malloc(sizeof (ENV)))) {
113 syslog(LOG_CRIT, "%m");
116 SLIST_INIT(&self->e_head);
118 self->e_committed = 0;
124 env_put(ENV *self, const char *s)
126 struct env_entry *env;
128 if (!(env = malloc(sizeof (struct env_entry))) ||
129 !(env->ee_env = strdup(s))) {
130 syslog(LOG_CRIT, "%m");
131 return PAM_SERVICE_ERR;
133 SLIST_INSERT_HEAD(&self->e_head, env, ee_entries);
140 env_swap(const ENV *self, int which)
142 environ = which ? self->e_environ_new : self->e_environ_orig;
147 env_commit(ENV *self)
153 for (v = environ, n = 0; v && *v; v++, n++)
155 if (!(v = malloc((n + self->e_count + 1) * sizeof (char *)))) {
156 syslog(LOG_CRIT, "%m");
157 return PAM_SERVICE_ERR;
159 self->e_committed = 1;
160 (void)memcpy(v, environ, n * sizeof (char *));
161 SLIST_FOREACH(p, &self->e_head, ee_entries)
164 self->e_environ_orig = environ;
165 self->e_environ_new = v;
172 env_destroy(ENV *self)
177 while ((p = SLIST_FIRST(&self->e_head))) {
180 SLIST_REMOVE_HEAD(&self->e_head, ee_entries);
182 if (self->e_committed)
183 free(self->e_environ_new);
189 env_cleanup(pam_handle_t *pamh, void *data, int error_status)
196 typedef struct passwd PASSWD;
205 char *comment_priv; /* on private key */
206 char *comment_pub; /* on public key */
207 char *identity; /* user's identity file */
208 RSA *key; /* user's private key */
209 int options; /* module options */
210 const char *pass; /* passphrase */
211 char *prompt; /* passphrase prompt */
212 RSA *public_key; /* user's public key */
213 const PASSWD *pwent; /* user's passwd entry */
214 PASSWD *pwent_keep; /* our own copy */
215 int retval; /* from calls */
216 uid_t saved_uid; /* caller's uid */
217 const char *user; /* username */
221 pam_std_option(&options, *argv++);
222 if ((retval = pam_get_user(pamh, &user, NULL)) != PAM_SUCCESS)
224 if (!((pwent = getpwnam(user)) && pwent->pw_dir)) {
228 /* locate the user's private key file */
229 if (!asprintf(&identity, "%s/%s", pwent->pw_dir,
230 SSH_CLIENT_IDENTITY)) {
231 syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
232 return PAM_SERVICE_ERR;
235 * Fail unless we can load the public key. Change to the
236 * owner's UID to appease load_public_key().
239 public_key = RSA_new();
240 saved_uid = getuid();
241 (void)setreuid(pwent->pw_uid, saved_uid);
242 retval = load_public_key(identity, public_key, &comment_pub);
243 (void)setuid(saved_uid);
248 RSA_free(public_key);
249 /* build the passphrase prompt */
250 retval = asprintf(&prompt, NEED_PASSPHRASE, identity, comment_pub);
253 syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
255 return PAM_SERVICE_ERR;
257 /* pass prompt message to application and receive passphrase */
258 retval = pam_get_pass(pamh, &pass, prompt, options);
260 if (retval != PAM_SUCCESS) {
265 * Try to decrypt the private key with the passphrase provided.
266 * If success, the user is authenticated.
268 (void)setreuid(pwent->pw_uid, saved_uid);
269 retval = load_private_key(identity, pass, key, &comment_priv);
271 (void)setuid(saved_uid);
275 * Save the key and comment to pass to ssh-agent in the session
278 if ((retval = pam_set_data(pamh, "ssh_private_key", key,
279 rsa_cleanup)) != PAM_SUCCESS) {
284 if ((retval = pam_set_data(pamh, "ssh_key_comment", comment_priv,
285 ssh_cleanup)) != PAM_SUCCESS) {
290 * Copy the passwd entry (in case successive calls are made)
291 * and save it for the session phase.
293 if (!(pwent_keep = malloc(sizeof *pwent))) {
294 syslog(LOG_CRIT, "%m");
295 return PAM_SERVICE_ERR;
297 (void)memcpy(pwent_keep, pwent, sizeof *pwent_keep);
298 if ((retval = pam_set_data(pamh, "ssh_passwd_entry", pwent_keep,
299 ssh_cleanup)) != PAM_SUCCESS) {
318 typedef AuthenticationConnection AC;
327 AC *ac; /* to ssh-agent */
328 char *comment; /* on private key */
329 char *env_end; /* end of env */
330 char *env_file; /* to store env */
331 FILE *env_fp; /* env_file handle */
332 RSA *key; /* user's private key */
333 FILE *pipe; /* ssh-agent handle */
334 const PASSWD *pwent; /* user's passwd entry */
335 int retval; /* from calls */
336 uid_t saved_uid; /* caller's uid */
337 ENV *ssh_env; /* env handle */
338 const char *tty; /* tty or display name */
339 char hname[MAXHOSTNAMELEN]; /* local hostname */
340 char parse[BUFSIZ]; /* commands output */
342 /* dump output of ssh-agent in ~/.ssh */
343 if ((retval = pam_get_data(pamh, "ssh_passwd_entry",
344 (const void **)&pwent)) != PAM_SUCCESS)
346 /* use the tty or X display name in the filename */
347 if ((retval = pam_get_item(pamh, PAM_TTY, (const void **)&tty))
350 if (*tty == ':' && gethostname(hname, sizeof hname) == 0) {
351 if (asprintf(&env_file, "%s/.ssh/agent-%s%s",
352 pwent->pw_dir, hname, tty) == -1) {
353 syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
354 return PAM_SERVICE_ERR;
356 } else if (asprintf(&env_file, "%s/.ssh/agent-%s", pwent->pw_dir,
358 syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
359 return PAM_SERVICE_ERR;
361 /* save the filename so we can delete the file on session close */
362 if ((retval = pam_set_data(pamh, "ssh_agent_env", env_file,
363 ssh_cleanup)) != PAM_SUCCESS) {
367 /* start the agent as the user */
368 saved_uid = geteuid();
369 (void)seteuid(pwent->pw_uid);
370 if ((env_fp = fopen(env_file, "w")))
371 (void)chmod(env_file, S_IRUSR);
372 pipe = popen(PATH_SSH_AGENT, "r");
373 (void)seteuid(saved_uid);
375 syslog(LOG_ERR, "%s: %s: %m", MODULE_NAME, PATH_SSH_AGENT);
377 (void)fclose(env_fp);
378 return PAM_SESSION_ERR;
380 if (!(ssh_env = env_new()))
381 return PAM_SESSION_ERR;
382 if ((retval = pam_set_data(pamh, "ssh_env_handle", ssh_env,
383 env_cleanup)) != PAM_SUCCESS)
385 while (fgets(parse, sizeof parse, pipe)) {
387 (void)fputs(parse, env_fp);
389 * Save environment for application with pam_putenv()
390 * but also with env_* functions for our own call to
391 * ssh_get_authentication_connection().
393 if (strchr(parse, '=') && (env_end = strchr(parse, ';'))) {
395 /* pass to the application ... */
396 if (!((retval = pam_putenv(pamh, parse)) ==
400 (void)fclose(env_fp);
401 env_destroy(ssh_env);
402 return PAM_SERVICE_ERR;
404 env_put(ssh_env, parse);
408 (void)fclose(env_fp);
409 switch (retval = pclose(pipe)) {
411 syslog(LOG_ERR, "%s: %s: %m", MODULE_NAME, PATH_SSH_AGENT);
412 env_destroy(ssh_env);
413 return PAM_SESSION_ERR;
417 syslog(LOG_ERR, "%s: cannot execute %s", MODULE_NAME,
419 env_destroy(ssh_env);
420 return PAM_SESSION_ERR;
422 syslog(LOG_ERR, "%s: %s exited with status %d",
423 MODULE_NAME, PATH_SSH_AGENT, WEXITSTATUS(retval));
424 env_destroy(ssh_env);
425 return PAM_SESSION_ERR;
427 /* connect to the agent and hand off the private key */
428 if ((retval = pam_get_data(pamh, "ssh_private_key",
429 (const void **)&key)) != PAM_SUCCESS ||
430 (retval = pam_get_data(pamh, "ssh_key_comment",
431 (const void **)&comment)) != PAM_SUCCESS ||
432 (retval = env_commit(ssh_env)) != PAM_SUCCESS) {
433 env_destroy(ssh_env);
436 if (!(ac = ssh_get_authentication_connection())) {
437 syslog(LOG_ERR, "%s: could not connect to agent",
439 env_destroy(ssh_env);
440 return PAM_SESSION_ERR;
442 retval = ssh_add_identity(ac, key, comment);
443 ssh_close_authentication_connection(ac);
444 env_swap(ssh_env, 0);
445 return retval ? PAM_SUCCESS : PAM_SESSION_ERR;
450 pam_sm_close_session(
456 const char *env_file; /* ssh-agent environment */
457 int retval; /* from calls */
458 ENV *ssh_env; /* env handle */
460 if ((retval = pam_get_data(pamh, "ssh_env_handle",
461 (const void **)&ssh_env)) != PAM_SUCCESS)
463 env_swap(ssh_env, 1);
465 retval = system(PATH_SSH_AGENT " -k");
466 env_destroy(ssh_env);
469 syslog(LOG_ERR, "%s: %s -k: %m", MODULE_NAME,
471 return PAM_SESSION_ERR;
475 syslog(LOG_ERR, "%s: cannot execute %s -k", MODULE_NAME,
477 return PAM_SESSION_ERR;
479 syslog(LOG_ERR, "%s: %s -k exited with status %d",
480 MODULE_NAME, PATH_SSH_AGENT, WEXITSTATUS(retval));
481 return PAM_SESSION_ERR;
483 /* retrieve environment filename, then remove the file */
484 if ((retval = pam_get_data(pamh, "ssh_agent_env",
485 (const void **)&env_file)) != PAM_SUCCESS)
487 (void)unlink(env_file);
492 PAM_MODULE_ENTRY(MODULE_NAME);