Add the DragonFly cvs id and perform general cleanups on cvs/rcs/sccs ids. Most
[dragonfly.git] / lib / libpam / modules / pam_ssh / pam_ssh.c
1 /*-
2  * Copyright (c) 1999, 2000 Andrew J. Korty
3  * All rights reserved.
4  * Copyright (c) 2001,2002 Networks Associates Technology, 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  * $Id: pam_ssh.c,v 1.23 2001/08/20 01:44:02 akorty Exp $
37  * $FreeBSD: src/lib/libpam/modules/pam_ssh/pam_ssh.c,v 1.28.2.4 2002/07/12 09:24:56 des Exp $
38  * $DragonFly: src/lib/libpam/modules/pam_ssh/Attic/pam_ssh.c,v 1.2 2003/06/17 04:26:50 dillon Exp $
39  */
40
41 #include <sys/param.h>
42 #include <sys/stat.h>
43 #include <sys/wait.h>
44
45 #include <fcntl.h>
46 #include <pwd.h>
47 #include <signal.h>
48 #include <stdio.h>
49 #include <stdlib.h>
50 #include <string.h>
51 #include <unistd.h>
52
53 #define PAM_SM_AUTH
54 #define PAM_SM_SESSION
55
56 #include <security/pam_appl.h>
57 #include <security/pam_modules.h>
58 #include <security/pam_mod_misc.h>
59
60 #include <openssl/dsa.h>
61 #include <openssl/evp.h>
62
63 #include "key.h"
64 #include "authfd.h"
65 #include "authfile.h"
66 #include "log.h"
67 #include "pam_ssh.h"
68
69 enum {
70         PAM_OPT_KEYFILES = PAM_OPT_STD_MAX
71 };
72
73 static struct opttab other_options[] = {
74         { "keyfiles",   PAM_OPT_KEYFILES },
75         { NULL, 0 }
76 };
77
78 static void key_cleanup(pam_handle_t *, void *, int);
79 static void ssh_cleanup(pam_handle_t *, void *, int);
80
81 /*
82  * Generic cleanup function for OpenSSH "Key" type.
83  */
84
85 static void
86 key_cleanup(pam_handle_t *pamh __unused, void *data, int err __unused)
87 {
88         if (data)
89                 key_free(data);
90 }
91
92
93 /*
94  * Generic PAM cleanup function for this module.
95  */
96
97 static void
98 ssh_cleanup(pam_handle_t *pamh __unused, void *data, int err __unused)
99 {
100         if (data)
101                 free(data);
102 }
103
104
105 /*
106  * Authenticate a user's key by trying to decrypt it with the password
107  * provided.  The key and its comment are then stored for later
108  * retrieval by the session phase.  An increasing index is embedded in
109  * the PAM variable names so this function may be called multiple times
110  * for multiple keys.
111  */
112
113 static int
114 auth_via_key(pam_handle_t *pamh, const char *file, const char *dir,
115     const struct passwd *user, const char *pass, struct options *options)
116 {
117         char *comment;          /* private key comment */
118         char *data_name;        /* PAM state */
119         static int key_idx = 0; /* for saved keys */
120         Key *key;               /* user's key */
121         char *path;             /* to key files */
122         int retval;             /* from calls */
123
124         /* locate the user's private key file */
125
126         if (!asprintf(&path, "%s/%s", dir, file)) {
127                 PAM_LOG( "%m");
128                 return (PAM_SERVICE_ERR);
129         }
130
131         /* Try to decrypt the private key with the passphrase provided.  If
132            success, the user is authenticated. */
133
134         comment = NULL;
135         if ((retval = openpam_borrow_cred(pamh, user)) != PAM_SUCCESS)
136                 return (retval);
137         key = key_load_private(path, pass, &comment);
138         openpam_restore_cred(pamh);
139         free(path);
140         if (!comment)
141                 comment = strdup(file);
142         if (!key) {
143                 free(comment);
144                 return (PAM_AUTH_ERR);
145         }
146
147         /* save the key and comment to pass to ssh-agent in the session
148            phase */
149
150         if (!asprintf(&data_name, "ssh_private_key_%d", key_idx)) {
151                 PAM_LOG( "%m");
152                 free(comment);
153                 return (PAM_SERVICE_ERR);
154         }
155         retval = pam_set_data(pamh, data_name, key, key_cleanup);
156         free(data_name);
157         if (retval != PAM_SUCCESS) {
158                 key_free(key);
159                 free(comment);
160                 return (retval);
161         }
162         if (!asprintf(&data_name, "ssh_key_comment_%d", key_idx)) {
163                 PAM_LOG( "%m");
164                 free(comment);
165                 return (PAM_SERVICE_ERR);
166         }
167         retval = pam_set_data(pamh, data_name, comment, ssh_cleanup);
168         free(data_name);
169         if (retval != PAM_SUCCESS) {
170                 free(comment);
171                 return (retval);
172         }
173
174         ++key_idx;
175         return (PAM_SUCCESS);
176 }
177
178
179 /*
180  * Add the keys stored by auth_via_key() to the agent connected to the
181  * socket provided.
182  */
183
184 static int
185 add_keys(pam_handle_t *pamh, struct options *options)
186 {
187         AuthenticationConnection *ac;   /* connection to ssh-agent */
188         char *comment;                  /* private key comment */
189         char *data_name;                /* PAM state */
190         int final;                      /* final return value */
191         int key_idx;                    /* for saved keys */
192         Key *key;                       /* user's private key */
193         int retval;                     /* from calls */
194
195         /*
196          * Connect to the agent.
197          *
198          * XXX Because ssh_get_authentication_connection() gets the
199          * XXX agent parameters from the environment, we have to
200          * XXX temporarily replace the environment with the PAM
201          * XXX environment list.  This is a hack.
202          */
203         {
204                 extern char **environ;
205                 char **saved, **evp;
206
207                 saved = environ;
208                 if ((environ = pam_getenvlist(pamh)) == NULL) {
209                         environ = saved;
210                         PAM_LOG( "%m");
211                         return (PAM_BUF_ERR);
212                 }
213                 ac = ssh_get_authentication_connection();
214                 for (evp = environ; *evp; evp++)
215                         free(*evp);
216                 free(environ);
217                 environ = saved;
218         }
219         if (!ac) {
220                 PAM_LOG( "%m");
221                 return (PAM_SESSION_ERR);
222         }
223
224         /* hand off each private key to the agent */
225
226         final = 0;
227         for (key_idx = 0; ; key_idx++) {
228                 if (!asprintf(&data_name, "ssh_private_key_%d", key_idx)) {
229                         PAM_LOG( "%m");
230                         ssh_close_authentication_connection(ac);
231                         return (PAM_SERVICE_ERR);
232                 }
233                 retval = pam_get_data(pamh, data_name, (const void **)&key);
234                 free(data_name);
235                 if (retval != PAM_SUCCESS)
236                         break;
237                 if (!asprintf(&data_name, "ssh_key_comment_%d", key_idx)) {
238                         PAM_LOG( "%m");
239                         ssh_close_authentication_connection(ac);
240                         return (PAM_SERVICE_ERR);
241                 }
242                 retval = pam_get_data(pamh, data_name,
243                     (const void **)&comment);
244                 free(data_name);
245                 if (retval != PAM_SUCCESS)
246                         break;
247                 retval = ssh_add_identity(ac, key, comment);
248                 if (!final)
249                         final = retval;
250         }
251         ssh_close_authentication_connection(ac);
252
253         return (final ? PAM_SUCCESS : PAM_SESSION_ERR);
254 }
255
256
257 PAM_EXTERN int
258 pam_sm_authenticate(pam_handle_t *pamh, int flags __unused,
259     int argc __unused, const char *argv[] __unused)
260 {
261         struct options options;
262         int authenticated;              /* user authenticated? */
263         char *dotdir;                   /* .ssh dir name */
264         char *file;                     /* current key file */
265         char *kfspec;                   /* list of key files to add */
266         char *keyfiles;
267         const char *pass;               /* passphrase */
268         const struct passwd *pwent;     /* user's passwd entry */
269         struct passwd *pwent_keep;      /* our own copy */
270         int retval;                     /* from calls */
271         const char *user;               /* username */
272
273         pam_std_option(&options, other_options, argc, argv);
274
275         if (!pam_test_option(&options, PAM_OPT_KEYFILES, &kfspec)) {
276                 kfspec = DEF_KEYFILES;
277         }
278
279         if ((retval = pam_get_user(pamh, &user, NULL)) != PAM_SUCCESS)
280                 return (retval);
281         if (user == NULL || (pwent = getpwnam(user)) == NULL ||
282             pwent->pw_dir == NULL || pwent->pw_dir[0] == '\0')
283                 return (PAM_AUTH_ERR);
284
285         /* pass prompt message to application and receive passphrase */
286
287         retval = pam_get_pass(pamh, &pass, NEED_PASSPHRASE, &options);
288         if (retval != PAM_SUCCESS)
289                 return (retval);
290
291         OpenSSL_add_all_algorithms(); /* required for DSA */
292
293         /* any key will authenticate us, but if we can decrypt all of the
294            specified keys, we'll do so here so we can cache them in the
295            session phase */
296
297         if (!asprintf(&dotdir, "%s/%s", pwent->pw_dir, SSH_CLIENT_DIR)) {
298                 PAM_LOG( "%m");
299                 return (PAM_SERVICE_ERR);
300         }
301         authenticated = 0;
302         keyfiles = strdup(kfspec);
303         for (file = strtok(keyfiles, SEP_KEYFILES); file;
304              file = strtok(NULL, SEP_KEYFILES))
305                 if (auth_via_key(pamh, file, dotdir, pwent, pass, &options) ==
306                     PAM_SUCCESS)
307                         authenticated++;
308         free(keyfiles);
309         free(dotdir);
310         if (!authenticated)
311                 return (PAM_AUTH_ERR);
312
313         /* copy the passwd entry (in case successive calls are made) and
314            save it for the session phase */
315
316         if (!(pwent_keep = malloc(sizeof *pwent))) {
317                 PAM_LOG( "%m");
318                 return (PAM_SERVICE_ERR);
319         }
320         (void) memcpy(pwent_keep, pwent, sizeof *pwent_keep);
321         if ((retval = pam_set_data(pamh, "ssh_passwd_entry", pwent_keep,
322             ssh_cleanup)) != PAM_SUCCESS) {
323                 free(pwent_keep);
324                 return (retval);
325         }
326
327         return (PAM_SUCCESS);
328 }
329
330
331 PAM_EXTERN int
332 pam_sm_setcred(pam_handle_t *pamh __unused, int flags __unused,
333     int argc __unused, const char *argv[] __unused)
334 {
335
336         return (PAM_SUCCESS);
337 }
338
339
340 PAM_EXTERN int
341 pam_sm_open_session(pam_handle_t *pamh, int flags __unused,
342     int argc __unused, const char *argv[] __unused)
343 {
344         struct options options;
345         char *agent_socket;             /* agent socket */
346         char *env_end;                  /* end of env */
347         FILE *env_read;                 /* env data source */
348         char env_string[BUFSIZ];        /* environment string */
349         char *env_value;                /* envariable value */
350         int env_write;                  /* env file descriptor */
351         char hname[MAXHOSTNAMELEN];     /* local hostname */
352         int no_link;                    /* link per-agent file? */
353         char *per_agent;                /* to store env */
354         char *per_session;              /* per-session filename */
355         char *agent_pid;                /* agent pid */
356         const struct passwd *pwent;     /* user's passwd entry */
357         int retval;                     /* from calls */
358         int start_agent;                /* start agent? */
359         const char *tty;                /* tty or display name */
360
361         pam_std_option(&options, other_options, argc, argv);
362
363         /* dump output of ssh-agent in ~/.ssh */
364         if ((retval = pam_get_data(pamh, "ssh_passwd_entry",
365             (const void **)&pwent)) != PAM_SUCCESS)
366                 return (retval);
367
368         /*
369          * Use reference counts to limit agents to one per user per host.
370          *
371          * Technique: Create an environment file containing
372          * information about the agent.  Only one file is created, but
373          * it may be given many names.  One name is given for the
374          * agent itself, agent-<host>.  Another name is given for each
375          * session, agent-<host>-<display> or agent-<host>-<tty>.  We
376          * delete the per-session filename on session close, and when
377          * the link count goes to unity on the per-agent file, we
378          * delete the file and kill the agent.
379          */
380
381         /* the per-agent file contains just the hostname */
382
383         (void) gethostname(hname, sizeof hname);
384         if (asprintf(&per_agent, "%s/.ssh/agent-%s", pwent->pw_dir, hname)
385             == -1) {
386                 PAM_LOG( "%m");
387                 return (PAM_SERVICE_ERR);
388         }
389
390         /* save the per-agent filename in case we want to delete it on
391            session close */
392
393         if ((retval = pam_set_data(pamh, "ssh_agent_env_agent", per_agent,
394             ssh_cleanup)) != PAM_SUCCESS) {
395                 free(per_agent);
396                 return (retval);
397         }
398
399         /* take on the user's privileges for writing files and starting the
400            agent */
401
402         if ((retval = openpam_borrow_cred(pamh, pwent)) != PAM_SUCCESS)
403                 return (retval);
404
405         /* Try to create the per-agent file or open it for reading if it
406            exists.  If we can't do either, we won't try to link a
407            per-session filename later.  Start the agent if we can't open
408            the file for reading. */
409
410         env_write = no_link = 0;
411         env_read = NULL;
412         if ((env_write = open(per_agent, O_CREAT | O_EXCL | O_WRONLY,
413             S_IRUSR)) < 0 && !(env_read = fopen(per_agent, "r")))
414                 no_link = 1;
415         if (env_read) {
416                 start_agent = 0;
417                 openpam_restore_cred(pamh);
418         } else {
419                 start_agent = 1;
420                 env_read = popen(SSH_AGENT, "r");
421                 openpam_restore_cred(pamh);
422                 if (!env_read) {
423                         PAM_LOG( "%s: %m", SSH_AGENT);
424                         if (env_write >= 0)
425                                 (void) close(env_write);
426                         return (PAM_SESSION_ERR);
427                 }
428         }
429
430         /* save environment for application with pam_putenv() */
431
432         agent_socket = NULL;
433         while (fgets(env_string, sizeof env_string, env_read)) {
434
435                 /* parse environment definitions */
436
437                 if (env_write >= 0)
438                         (void) write(env_write, env_string,
439                             strlen(env_string));
440                 if (!(env_value = strchr(env_string, '=')) ||
441                     !(env_end = strchr(env_value, ';')))
442                         continue;
443                 *env_end = '\0';
444
445                 /* pass to the application */
446
447                 if (!((retval = pam_putenv(pamh, env_string)) ==
448                     PAM_SUCCESS)) {
449                         if (start_agent)
450                                 (void) pclose(env_read);
451                         else
452                                 (void) fclose(env_read);
453                         if (env_write >= 0)
454                                 (void) close(env_write);
455                         if (agent_socket)
456                                 free(agent_socket);
457                         return (PAM_SERVICE_ERR);
458                 }
459
460                 *env_value++ = '\0';
461
462                 /* save the agent socket so we can connect to it and add
463                    the keys as well as the PID so we can kill the agent on
464                    session close. */
465
466                 if (strcmp(&env_string[strlen(env_string) -
467                     strlen(ENV_SOCKET_SUFFIX)], ENV_SOCKET_SUFFIX) == 0 &&
468                     !(agent_socket = strdup(env_value))) {
469                         PAM_LOG( "%m");
470                         if (start_agent)
471                                 (void) pclose(env_read);
472                         else
473                                 (void) fclose(env_read);
474                         if (env_write >= 0)
475                                 (void) close(env_write);
476                         if (agent_socket)
477                                 free(agent_socket);
478                         return (PAM_SERVICE_ERR);
479                 } else if (strcmp(&env_string[strlen(env_string) -
480                     strlen(ENV_PID_SUFFIX)], ENV_PID_SUFFIX) == 0 &&
481                     ((agent_pid = strdup(env_value)) == NULL ||
482                     (retval = pam_set_data(pamh, "ssh_agent_pid",
483                     agent_pid, ssh_cleanup)) != PAM_SUCCESS)) {
484                         if (start_agent)
485                                 (void) pclose(env_read);
486                         else
487                                 (void) fclose(env_read);
488                         if (env_write >= 0)
489                                 (void) close(env_write);
490                         if (agent_socket)
491                                 free(agent_socket);
492                         if (agent_pid)
493                                 free(agent_pid);
494                         return (retval);
495                 }
496
497         }
498         if (env_write >= 0)
499                 (void) close(env_write);
500
501         if (start_agent) {
502                 switch (retval = pclose(env_read)) {
503                 case -1:
504                         PAM_LOG( "%s: %m", SSH_AGENT);
505                         if (agent_socket)
506                                 free(agent_socket);
507                         return (PAM_SESSION_ERR);
508                 case 0:
509                         break;
510                 case 127:
511                         PAM_LOG( "cannot execute %s",
512                             SSH_AGENT);
513                         if (agent_socket)
514                                 free(agent_socket);
515                         return (PAM_SESSION_ERR);
516                 default:
517                         PAM_LOG( "%s exited %s %d",
518                             SSH_AGENT, WIFSIGNALED(retval) ? "on signal" :
519                             "with status", WIFSIGNALED(retval) ?
520                             WTERMSIG(retval) : WEXITSTATUS(retval));
521                         if (agent_socket)
522                                 free(agent_socket);
523                         return (PAM_SESSION_ERR);
524                 }
525         } else
526                 (void) fclose(env_read);
527
528         if (!agent_socket)
529                 return (PAM_SESSION_ERR);
530
531         if (start_agent && (retval = add_keys(pamh, &options))
532             != PAM_SUCCESS)
533                 return (retval);
534         free(agent_socket);
535
536         /* if we couldn't access the per-agent file, don't link a
537            per-session filename to it */
538
539         if (no_link)
540                 return (PAM_SUCCESS);
541
542         /* the per-session file contains the display name or tty name as
543            well as the hostname */
544
545         if ((retval = pam_get_item(pamh, PAM_TTY, (const void **)&tty))
546             != PAM_SUCCESS)
547                 return (retval);
548         if (asprintf(&per_session, "%s/.ssh/agent-%s-%s", pwent->pw_dir,
549             hname, tty) == -1) {
550                 PAM_LOG( "%m");
551                 return (PAM_SERVICE_ERR);
552         }
553
554         /* save the per-session filename so we can delete it on session
555            close */
556
557         if ((retval = pam_set_data(pamh, "ssh_agent_env_session",
558             per_session, ssh_cleanup)) != PAM_SUCCESS) {
559                 free(per_session);
560                 return (retval);
561         }
562
563         (void) unlink(per_session);             /* remove cruft */
564         (void) link(per_agent, per_session);
565
566         return (PAM_SUCCESS);
567 }
568
569
570 PAM_EXTERN int
571 pam_sm_close_session(pam_handle_t *pamh, int flags __unused,
572     int argc __unused, const char *argv[] __unused)
573 {
574         struct options options;
575         const char *env_file;           /* ssh-agent environment */
576         pid_t pid;                      /* ssh-agent process id */
577         int retval;                     /* from calls */
578         const char *ssh_agent_pid;      /* ssh-agent pid string */
579         struct stat sb;                 /* to check st_nlink */
580
581         pam_std_option(&options, other_options, argc, argv);
582
583         if ((retval = pam_get_data(pamh, "ssh_agent_env_session",
584             (const void **)&env_file)) == PAM_SUCCESS && env_file)
585                 (void) unlink(env_file);
586
587         /* Retrieve per-agent filename and check link count.  If it's
588            greater than unity, other sessions are still using this
589            agent. */
590
591         if ((retval = pam_get_data(pamh, "ssh_agent_env_agent",
592             (const void **)&env_file)) == PAM_SUCCESS && env_file &&
593             stat(env_file, &sb) == 0) {
594                 if (sb.st_nlink > 1)
595                         return (PAM_SUCCESS);
596                 (void) unlink(env_file);
597         }
598
599         /* retrieve the agent's process id */
600
601         if ((retval = pam_get_data(pamh, "ssh_agent_pid",
602             (const void **)&ssh_agent_pid)) != PAM_SUCCESS)
603                 return (retval);
604
605         /* Kill the agent.  SSH's ssh-agent does not have a -k option, so
606            just call kill(). */
607
608         pid = atoi(ssh_agent_pid);
609         if (pid <= 0)
610                 return (PAM_SESSION_ERR);
611         if (kill(pid, SIGTERM) != 0) {
612                 PAM_LOG( "%s: %m", ssh_agent_pid);
613                 return (PAM_SESSION_ERR);
614         }
615
616         return (PAM_SUCCESS);
617 }
618
619 PAM_MODULE_ENTRY(MODULE_NAME);