]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - lib/libpam/modules/pam_ssh/pam_ssh.c
unfinished sblive driver, playback/mixer only for now - not enabled in
[FreeBSD/FreeBSD.git] / lib / libpam / modules / pam_ssh / pam_ssh.c
1 /*-
2  * Copyright (c) 1999, 2000 Andrew J. Korty
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
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.
13  *
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
24  * SUCH DAMAGE.
25  *
26  * $FreeBSD$
27  *
28  */
29
30
31 #include <sys/param.h>
32 #include <sys/queue.h>
33 #include <sys/stat.h>
34
35 #include <fcntl.h>
36 #include <paths.h>
37 #include <pwd.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <unistd.h>
42
43 #define PAM_SM_AUTH
44 #define PAM_SM_SESSION
45 #include <security/pam_modules.h>
46 #include <security/pam_mod_misc.h>
47
48 #include "includes.h"
49 #include "rsa.h"
50 #include "ssh.h"
51 #include "authfd.h"
52
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"
56
57
58 void
59 rsa_cleanup(pam_handle_t *pamh, void *data, int error_status)
60 {
61         if (data)
62                 RSA_free(data);
63 }
64
65
66 void
67 ssh_cleanup(pam_handle_t *pamh, void *data, int error_status)
68 {
69         if (data)
70                 free(data);
71 }
72
73
74 /*
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()
82  * have been called.
83  * 
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).
87  */
88
89 SLIST_HEAD(env_head, env_entry);
90
91 struct env_entry {
92         char                    *ee_env;
93         SLIST_ENTRY(env_entry)   ee_entries;
94 };
95
96 typedef struct env {
97         char            **e_environ_orig;
98         char            **e_environ_new;
99         int               e_count;
100         struct env_head   e_head;
101         int               e_committed;
102 } ENV;
103
104 extern char **environ;
105
106
107 static ENV *
108 env_new(void)
109 {
110         ENV     *self;
111
112         if (!(self = malloc(sizeof (ENV)))) {
113                 syslog(LOG_CRIT, "%m");
114                 return NULL;
115         }
116         SLIST_INIT(&self->e_head);
117         self->e_count = 0;
118         self->e_committed = 0;
119         return self;
120 }
121
122
123 static int
124 env_put(ENV *self, const char *s)
125 {
126         struct env_entry        *env;
127
128         if (!(env = malloc(sizeof (struct env_entry))) ||
129             !(env->ee_env = strdup(s))) {
130                 syslog(LOG_CRIT, "%m");
131                 return PAM_SERVICE_ERR;
132         }
133         SLIST_INSERT_HEAD(&self->e_head, env, ee_entries);
134         ++self->e_count;
135         return PAM_SUCCESS;
136 }
137
138
139 static void
140 env_swap(const ENV *self, int which)
141 {
142         environ = which ? self->e_environ_new : self->e_environ_orig;
143 }
144
145
146 static int
147 env_commit(ENV *self)
148 {
149         int                       n;
150         struct env_entry         *p;
151         char                    **v;
152
153         for (v = environ, n = 0; v && *v; v++, n++)
154                 ;
155         if (!(v = malloc((n + self->e_count + 1) * sizeof (char *)))) {
156                 syslog(LOG_CRIT, "%m");
157                 return PAM_SERVICE_ERR;
158         }
159         self->e_committed = 1;
160         (void)memcpy(v, environ, n * sizeof (char *));
161         SLIST_FOREACH(p, &self->e_head, ee_entries)
162                 v[n++] = p->ee_env;
163         v[n] = NULL;
164         self->e_environ_orig = environ;
165         self->e_environ_new = v;
166         env_swap(self, 1);
167         return PAM_SUCCESS;
168 }
169
170
171 static void
172 env_destroy(ENV *self)
173 {
174         struct env_entry         *p;
175
176         env_swap(self, 0);
177         while ((p = SLIST_FIRST(&self->e_head))) {
178                 free(p->ee_env);
179                 free(p);
180                 SLIST_REMOVE_HEAD(&self->e_head, ee_entries);
181         }
182         if (self->e_committed)
183                 free(self->e_environ_new);
184         free(self);
185 }
186
187
188 void
189 env_cleanup(pam_handle_t *pamh, void *data, int error_status)
190 {
191         if (data)
192                 env_destroy(data);
193 }
194
195
196 typedef struct passwd PASSWD;
197
198 PAM_EXTERN int
199 pam_sm_authenticate(
200         pam_handle_t     *pamh,
201         int               flags,
202         int               argc,
203         const char      **argv)
204 {
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 */
218
219         options = 0;
220         while (argc--)
221                 pam_std_option(&options, *argv++);
222         if ((retval = pam_get_user(pamh, &user, NULL)) != PAM_SUCCESS)
223                 return retval;
224         if (!((pwent = getpwnam(user)) && pwent->pw_dir)) {
225                 /* delay? */
226                 return PAM_AUTH_ERR;
227         }
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;
233         }
234         /*
235          * Fail unless we can load the public key.  Change to the
236          * owner's UID to appease load_public_key().
237          */
238         key = RSA_new();
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);
244         if (!retval) {
245                 free(identity);
246                 return PAM_AUTH_ERR;
247         }
248         RSA_free(public_key);
249         /* build the passphrase prompt */
250         retval = asprintf(&prompt, NEED_PASSPHRASE, identity, comment_pub);
251         free(comment_pub);
252         if (!retval) {
253                 syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
254                 free(identity);
255                 return PAM_SERVICE_ERR;
256         }
257         /* pass prompt message to application and receive passphrase */
258         retval = pam_get_pass(pamh, &pass, prompt, options);
259         free(prompt);
260         if (retval != PAM_SUCCESS) {
261                 free(identity);
262                 return retval;
263         }
264         /*
265          * Try to decrypt the private key with the passphrase provided.
266          * If success, the user is authenticated.
267          */
268         (void)setreuid(pwent->pw_uid, saved_uid);
269         retval = load_private_key(identity, pass, key, &comment_priv);
270         free(identity);
271         (void)setuid(saved_uid);
272         if (!retval)
273                 return PAM_AUTH_ERR;
274         /*
275          * Save the key and comment to pass to ssh-agent in the session
276          * phase.
277          */
278         if ((retval = pam_set_data(pamh, "ssh_private_key", key,
279             rsa_cleanup)) != PAM_SUCCESS) {
280                 RSA_free(key);
281                 free(comment_priv);
282                 return retval;
283         }
284         if ((retval = pam_set_data(pamh, "ssh_key_comment", comment_priv,
285             ssh_cleanup)) != PAM_SUCCESS) {
286                 free(comment_priv);
287                 return retval;
288         }
289         /*
290          * Copy the passwd entry (in case successive calls are made)
291          * and save it for the session phase.
292          */
293         if (!(pwent_keep = malloc(sizeof *pwent))) {
294                 syslog(LOG_CRIT, "%m");
295                 return PAM_SERVICE_ERR;
296         }
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) {
300                 free(pwent_keep);
301                 return retval;
302         }
303         return PAM_SUCCESS;
304 }
305
306
307 PAM_EXTERN int
308 pam_sm_setcred(
309         pam_handle_t     *pamh,
310         int               flags,
311         int               argc,
312         const char      **argv)
313 {
314         return PAM_SUCCESS;
315 }
316
317
318 typedef AuthenticationConnection AC;
319
320 PAM_EXTERN int
321 pam_sm_open_session(
322         pam_handle_t     *pamh,
323         int               flags,
324         int               argc,
325         const char      **argv)
326 {
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 */
341
342         /* dump output of ssh-agent in ~/.ssh */
343         if ((retval = pam_get_data(pamh, "ssh_passwd_entry",
344             (const void **)&pwent)) != PAM_SUCCESS)
345                 return retval;
346         /* use the tty or X display name in the filename */
347         if ((retval = pam_get_item(pamh, PAM_TTY, (const void **)&tty))
348             != PAM_SUCCESS)
349                 return retval;
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;
355                 }
356         } else if (asprintf(&env_file, "%s/.ssh/agent-%s", pwent->pw_dir,
357             tty) == -1) {
358                 syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
359                 return PAM_SERVICE_ERR;
360         }
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) {
364                 free(env_file);
365                 return retval;
366         }
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);
374         if (!pipe) {
375                 syslog(LOG_ERR, "%s: %s: %m", MODULE_NAME, PATH_SSH_AGENT);
376                 if (env_fp)
377                         (void)fclose(env_fp);
378                 return PAM_SESSION_ERR;
379         }
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)
384                 return retval;
385         while (fgets(parse, sizeof parse, pipe)) {
386                 if (env_fp)
387                         (void)fputs(parse, env_fp);
388                 /*
389                  * Save environment for application with pam_putenv()
390                  * but also with env_* functions for our own call to
391                  * ssh_get_authentication_connection().
392                  */
393                 if (strchr(parse, '=') && (env_end = strchr(parse, ';'))) {
394                         *env_end = '\0';
395                         /* pass to the application ... */
396                         if (!((retval = pam_putenv(pamh, parse)) ==
397                             PAM_SUCCESS)) {
398                                 (void)pclose(pipe);
399                                 if (env_fp)
400                                         (void)fclose(env_fp);
401                                 env_destroy(ssh_env);
402                                 return PAM_SERVICE_ERR;
403                         }
404                         env_put(ssh_env, parse);
405                 }
406         }
407         if (env_fp)
408                 (void)fclose(env_fp);
409         switch (retval = pclose(pipe)) {
410         case -1:
411                 syslog(LOG_ERR, "%s: %s: %m", MODULE_NAME, PATH_SSH_AGENT);
412                 env_destroy(ssh_env);
413                 return PAM_SESSION_ERR;
414         case 0:
415                 break;
416         case 127:
417                 syslog(LOG_ERR, "%s: cannot execute %s", MODULE_NAME,
418                     PATH_SSH_AGENT);
419                 env_destroy(ssh_env);
420                 return PAM_SESSION_ERR;
421         default:
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;
426         }
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);
434                 return retval;
435         }
436         if (!(ac = ssh_get_authentication_connection())) {
437                 syslog(LOG_ERR, "%s: could not connect to agent",
438                     MODULE_NAME);
439                 env_destroy(ssh_env);
440                 return PAM_SESSION_ERR;
441         }
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;
446 }
447
448
449 PAM_EXTERN int
450 pam_sm_close_session(
451         pam_handle_t     *pamh,
452         int               flags,
453         int               argc,
454         const char      **argv)
455 {
456         const char      *env_file;      /* ssh-agent environment */
457         int              retval;        /* from calls */
458         ENV             *ssh_env;       /* env handle */
459
460         if ((retval = pam_get_data(pamh, "ssh_env_handle",
461             (const void **)&ssh_env)) != PAM_SUCCESS)
462                 return retval;
463         env_swap(ssh_env, 1);
464         /* kill the agent */
465         retval = system(PATH_SSH_AGENT " -k");
466         env_destroy(ssh_env);
467         switch (retval) {
468         case -1:
469                 syslog(LOG_ERR, "%s: %s -k: %m", MODULE_NAME,
470                     PATH_SSH_AGENT);
471                 return PAM_SESSION_ERR;
472         case 0:
473                 break;
474         case 127:
475                 syslog(LOG_ERR, "%s: cannot execute %s -k", MODULE_NAME,
476                     PATH_SSH_AGENT);
477                 return PAM_SESSION_ERR;
478         default:
479                 syslog(LOG_ERR, "%s: %s -k exited with status %d",
480                     MODULE_NAME, PATH_SSH_AGENT, WEXITSTATUS(retval));
481                 return PAM_SESSION_ERR;
482         }
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)
486                 return retval;
487         (void)unlink(env_file);
488         return PAM_SUCCESS;
489 }
490
491
492 PAM_MODULE_ENTRY(MODULE_NAME);