xref: /freebsd/lib/libpam/modules/pam_ssh/pam_ssh.c (revision 5129159789cc9d7bc514e4546b88e3427695002d)
1 /*-
2  * Copyright (c) 1999 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 
33 #include <fcntl.h>
34 #include <paths.h>
35 #include <pwd.h>
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <unistd.h>
40 
41 #define	PAM_SM_AUTH
42 #define	PAM_SM_SESSION
43 #include <security/pam_modules.h>
44 #include <security/pam_mod_misc.h>
45 
46 #include "includes.h"
47 #include "rsa.h"
48 #include "ssh.h"
49 #include "authfd.h"
50 
51 #define	MODULE_NAME	"pam_ssh"
52 #define	NEED_PASSPHRASE	"Need passphrase for %s (%s).\nEnter passphrase: "
53 #define	PATH_SSH_AGENT	"__PREFIX__/bin/ssh-agent"
54 
55 
56 void
57 rsa_cleanup(pam_handle_t *pamh, void *data, int error_status)
58 {
59 	if (data)
60 		RSA_free(data);
61 }
62 
63 
64 void
65 ssh_cleanup(pam_handle_t *pamh, void *data, int error_status)
66 {
67 	if (data)
68 		free(data);
69 }
70 
71 
72 typedef struct passwd PASSWD;
73 
74 PAM_EXTERN int
75 pam_sm_authenticate(
76 	pam_handle_t	 *pamh,
77 	int		  flags,
78 	int		  argc,
79 	const char	**argv)
80 {
81 	char		*comment_priv;		/* on private key */
82 	char		*comment_pub;		/* on public key */
83 	char		*identity;		/* user's identity file */
84 	RSA		*key;			/* user's private key */
85 	int		 options;		/* module options */
86 	const char	*pass;			/* passphrase */
87 	char		*prompt;		/* passphrase prompt */
88 	RSA		*public_key;		/* user's public key */
89 	const PASSWD	*pwent;			/* user's passwd entry */
90 	PASSWD		*pwent_keep;		/* our own copy */
91 	int		 retval;		/* from calls */
92 	uid_t		 saved_uid;		/* caller's uid */
93 	const char	*user;			/* username */
94 
95 	options = 0;
96 	while (argc--)
97 		pam_std_option(&options, *argv++);
98 	if ((retval = pam_get_user(pamh, &user, NULL)) != PAM_SUCCESS)
99 		return retval;
100 	if (!((pwent = getpwnam(user)) && pwent->pw_dir)) {
101 		/* delay? */
102 		return PAM_AUTH_ERR;
103 	}
104 	/* locate the user's private key file */
105 	if (!asprintf(&identity, "%s/%s", pwent->pw_dir,
106 	    SSH_CLIENT_IDENTITY)) {
107 		syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
108 		return PAM_SERVICE_ERR;
109 	}
110 	/*
111 	 * Fail unless we can load the public key.  Change to the
112 	 * owner's UID to appease load_public_key().
113 	 */
114 	key = RSA_new();
115 	public_key = RSA_new();
116 	saved_uid = getuid();
117 	(void)setreuid(pwent->pw_uid, saved_uid);
118 	retval = load_public_key(identity, public_key, &comment_pub);
119 	(void)setuid(saved_uid);
120 	if (!retval) {
121 		free(identity);
122 		return PAM_AUTH_ERR;
123 	}
124 	RSA_free(public_key);
125 	/* build the passphrase prompt */
126 	retval = asprintf(&prompt, NEED_PASSPHRASE, identity, comment_pub);
127 	free(comment_pub);
128 	if (!retval) {
129 		syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
130 		free(identity);
131 		return PAM_SERVICE_ERR;
132 	}
133 	/* pass prompt message to application and receive passphrase */
134 	retval = pam_get_pass(pamh, &pass, prompt, options);
135 	free(prompt);
136 	if (retval != PAM_SUCCESS) {
137 		free(identity);
138 		return retval;
139 	}
140 	/*
141 	 * Try to decrypt the private key with the passphrase provided.
142 	 * If success, the user is authenticated.
143 	 */
144 	(void)setreuid(pwent->pw_uid, saved_uid);
145 	retval = load_private_key(identity, pass, key, &comment_priv);
146 	free(identity);
147 	(void)setuid(saved_uid);
148 	if (!retval)
149 		return PAM_AUTH_ERR;
150 	/*
151 	 * Save the key and comment to pass to ssh-agent in the session
152 	 * phase.
153 	 */
154 	if ((retval = pam_set_data(pamh, "ssh_private_key", key,
155 	    rsa_cleanup)) != PAM_SUCCESS) {
156 		RSA_free(key);
157 		free(comment_priv);
158 		return retval;
159 	}
160 	if ((retval = pam_set_data(pamh, "ssh_key_comment", comment_priv,
161 	    ssh_cleanup)) != PAM_SUCCESS) {
162 		free(comment_priv);
163 		return retval;
164 	}
165 	/*
166 	 * Copy the passwd entry (in case successive calls are made)
167 	 * and save it for the session phase.
168 	 */
169 	if (!(pwent_keep = malloc(sizeof *pwent))) {
170 		syslog(LOG_CRIT, "%m");
171 		return PAM_SERVICE_ERR;
172 	}
173 	(void)memcpy(pwent_keep, pwent, sizeof *pwent_keep);
174 	if ((retval = pam_set_data(pamh, "ssh_passwd_entry", pwent_keep,
175 	    ssh_cleanup)) != PAM_SUCCESS) {
176 		free(pwent_keep);
177 		return retval;
178 	}
179 	return PAM_SUCCESS;
180 }
181 
182 
183 PAM_EXTERN int
184 pam_sm_setcred(
185 	pam_handle_t	 *pamh,
186 	int		  flags,
187 	int		  argc,
188 	const char	**argv)
189 {
190 	return PAM_SUCCESS;
191 }
192 
193 
194 typedef AuthenticationConnection AC;
195 
196 PAM_EXTERN int
197 pam_sm_open_session(
198 	pam_handle_t	 *pamh,
199 	int		  flags,
200 	int		  argc,
201 	const char	**argv)
202 {
203 	AC		*ac;			/* to ssh-agent */
204 	char		*comment;		/* on private key */
205 	char		*env_end;		/* end of env */
206 	char		*env_file;		/* to store env */
207 	FILE		*env_fp;		/* env_file handle */
208 	RSA		*key;			/* user's private key */
209 	FILE		*pipe;			/* ssh-agent handle */
210 	const PASSWD	*pwent;			/* user's passwd entry */
211 	int		 retval;		/* from calls */
212 	uid_t		 saved_uid;		/* caller's uid */
213 	const char	*tty;			/* tty or display name */
214 	char		 hname[MAXHOSTNAMELEN];	/* local hostname */
215 	char		 parse[BUFSIZ];		/* commands output */
216 
217 	/* dump output of ssh-agent in ~/.ssh */
218 	if ((retval = pam_get_data(pamh, "ssh_passwd_entry",
219 	    (const void **)&pwent)) != PAM_SUCCESS)
220 		return retval;
221 	/* use the tty or X display name in the filename */
222 	if ((retval = pam_get_item(pamh, PAM_TTY, (const void **)&tty))
223 	    != PAM_SUCCESS)
224 		return retval;
225 	if (*tty == ':' && gethostname(hname, sizeof hname) == 0) {
226 		if (asprintf(&env_file, "%s/.ssh/agent-%s%s",
227 		    pwent->pw_dir, hname, tty) == -1) {
228 			syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
229 			return PAM_SERVICE_ERR;
230 		}
231 	} else if (asprintf(&env_file, "%s/.ssh/agent-%s", pwent->pw_dir,
232 	    tty) == -1) {
233 		syslog(LOG_CRIT, "%s: %m", MODULE_NAME);
234 		return PAM_SERVICE_ERR;
235 	}
236 	/* save the filename so we can delete the file on session close */
237 	if ((retval = pam_set_data(pamh, "ssh_agent_env", env_file,
238 	    ssh_cleanup)) != PAM_SUCCESS) {
239 		free(env_file);
240 		return retval;
241 	}
242 	/* start the agent as the user */
243 	saved_uid = geteuid();
244 	(void)seteuid(pwent->pw_uid);
245 	env_fp = fopen(env_file, "w");
246 	pipe = popen(PATH_SSH_AGENT, "r");
247 	(void)seteuid(saved_uid);
248 	if (!pipe) {
249 		syslog(LOG_ERR, "%s: %s: %m", MODULE_NAME, PATH_SSH_AGENT);
250 		if (env_fp)
251 			(void)fclose(env_fp);
252 		return PAM_SESSION_ERR;
253 	}
254 	while (fgets(parse, sizeof parse, pipe)) {
255 		if (env_fp)
256 			(void)fputs(parse, env_fp);
257 		/*
258 		 * Save environment for application with pam_putenv()
259 		 * but also with putenv() for our own call to
260 		 * ssh_get_authentication_connection().
261 		 */
262 		if (strchr(parse, '=') && (env_end = strchr(parse, ';'))) {
263 			*env_end = '\0';
264 			/* pass to the application ... */
265 			if (!((retval = pam_putenv(pamh, parse)) ==
266 			    PAM_SUCCESS && putenv(parse) == 0)) {
267 				(void)pclose(pipe);
268 				if (env_fp)
269 					(void)fclose(env_fp);
270 				return PAM_SERVICE_ERR;
271 			}
272 		}
273 	}
274 	if (env_fp)
275 		(void)fclose(env_fp);
276 	retval = pclose(pipe);
277 	if (retval > 0) {
278 		syslog(LOG_ERR, "%s: %s exited with status %d",
279 		    MODULE_NAME, PATH_SSH_AGENT, WEXITSTATUS(retval));
280 		return PAM_SESSION_ERR;
281 	} else if (retval < 0) {
282 		syslog(LOG_ERR, "%s: %s: %m", MODULE_NAME, PATH_SSH_AGENT);
283 		return PAM_SESSION_ERR;
284 	}
285 	/* connect to the agent and hand off the private key */
286 	if ((retval = pam_get_data(pamh, "ssh_private_key",
287 	    (const void **)&key)) != PAM_SUCCESS)
288 		return retval;
289 	if ((retval = pam_get_data(pamh, "ssh_key_comment",
290 	    (const void **)&comment)) != PAM_SUCCESS)
291 		return retval;
292 	if (!(ac = ssh_get_authentication_connection())) {
293 		syslog(LOG_ERR, "%s: could not connect to agent",
294 		    MODULE_NAME);
295 		return PAM_SESSION_ERR;
296 	}
297 	retval = ssh_add_identity(ac, key, comment);
298 	ssh_close_authentication_connection(ac);
299 	return retval ? PAM_SUCCESS : PAM_SESSION_ERR;
300 }
301 
302 
303 PAM_EXTERN int
304 pam_sm_close_session(
305 	pam_handle_t	 *pamh,
306 	int		  flags,
307 	int		  argc,
308 	const char	**argv)
309 {
310 	const char	*env_file;	/* ssh-agent environment */
311 	int	 	 retval;	/* from calls */
312 
313 	/* kill the agent */
314 	if ((retval = system(PATH_SSH_AGENT " -k")) != 0) {
315 		syslog(LOG_ERR, "%s: %s -k exited with status %d",
316 		    MODULE_NAME, PATH_SSH_AGENT, WEXITSTATUS(retval));
317 		return PAM_SESSION_ERR;
318 	}
319 	/* retrieve environment filename, then remove the file */
320 	if ((retval = pam_get_data(pamh, "ssh_agent_env",
321 	    (const void **)&env_file)) != PAM_SUCCESS)
322 		return retval;
323 	(void)unlink(env_file);
324 	return PAM_SUCCESS;
325 }
326 
327 
328 PAM_MODULE_ENTRY(MODULE_NAME);
329