1 /*
2  * Copyright 2003,2004,2005,2006,2007,2008,2012,2013 Red Hat, Inc.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, and the entire permission notice in its entirety,
9  *    including the disclaimer of warranties.
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  * 3. The name of the author may not be used to endorse or promote
14  *    products derived from this software without specific prior
15  *    written permission.
16  *
17  * ALTERNATIVELY, this product may be distributed under the terms of the
18  * GNU Lesser General Public License, in which case the provisions of the
19  * LGPL are required INSTEAD OF the above restrictions.
20  *
21  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
22  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
23  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN
24  * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
25  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
26  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
27  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
28  * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
30  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32 
33 #include "../config.h"
34 
35 #include <sys/stat.h>
36 #include <sys/types.h>
37 #include <limits.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <unistd.h>
42 
43 #ifdef HAVE_SECURITY_PAM_APPL_H
44 #include <security/pam_appl.h>
45 #endif
46 
47 #ifdef HAVE_SECURITY_PAM_MODULES_H
48 #include <security/pam_modules.h>
49 #endif
50 
51 #include KRB5_H
52 
53 #include "log.h"
54 #include "minikafs.h"
55 #include "options.h"
56 #include "stash.h"
57 #include "tokens.h"
58 #include "userinfo.h"
59 #include "v5.h"
60 #include "xstr.h"
61 
62 int
tokens_useful(void)63 tokens_useful(void)
64 {
65 	if (minikafs_has_afs()) {
66 		return 1;
67 	}
68 	return 0;
69 }
70 
71 static int
cell_is_in_option_list(struct _pam_krb5_options * options,const char * cell)72 cell_is_in_option_list(struct _pam_krb5_options *options, const char *cell)
73 {
74 	int i;
75 	for (i = 0; i < options->n_afs_cells; i++) {
76 		if (strcmp(cell, options->afs_cells[i].cell) == 0) {
77 			return 1;
78 		}
79 	}
80 	return 0;
81 }
82 
83 int
tokens_obtain(krb5_context context,struct _pam_krb5_stash * stash,struct _pam_krb5_options * options,struct _pam_krb5_user_info * info,int newpag)84 tokens_obtain(krb5_context context,
85 	      struct _pam_krb5_stash *stash,
86 	      struct _pam_krb5_options *options,
87 	      struct _pam_krb5_user_info *info, int newpag)
88 {
89 	int i, ret;
90 	unsigned int n;
91 	char localcell[LINE_MAX], homecell[LINE_MAX], homedir[LINE_MAX],
92 	     lnk[LINE_MAX];
93 	struct stat st;
94 	uid_t uid;
95 	const struct {
96 		const char *name; int method;
97 	} method_names[] = {
98 		{"2b", MINIKAFS_METHOD_V5_2B},
99 		{"rxk5", MINIKAFS_METHOD_RXK5}
100 	};
101 	int *methods, n_methods;
102 	const char *p, *q;
103 
104 	if (options->debug) {
105 		debug("obtaining afs tokens");
106 	}
107 	uid = options->user_check ? info->uid : getuid();
108 
109 	/* Check if AFS is running.  If it isn't, no other calls to minikafs
110 	 * will work, or even be safe to call. */
111 	if (!minikafs_has_afs()) {
112 		if (stat("/afs", &st) == 0) {
113 			warn("afs not running");
114 		} else {
115 			if (options->debug) {
116 				debug("afs not running");
117 			}
118 		}
119 		return PAM_SUCCESS;
120 	}
121 
122 	/* Create a PAG. */
123 	if (newpag) {
124 		if (options->debug) {
125 			debug("creating new PAG");
126 		}
127 		minikafs_setpag();
128 		stash->afspag = 1;
129 	}
130 
131 	/* Parse the token_strategy option. */
132 	methods = malloc((strlen(options->token_strategy) + 1) * sizeof(int));
133 	if (methods == NULL) {
134 		return PAM_BUF_ERR;
135 	}
136 	memset(methods, 0, (strlen(options->token_strategy) + 1) * sizeof(int));
137 	n_methods = 0;
138 	p = options->token_strategy;
139 	while (strlen(p) > 0) {
140 		q = p + strcspn(p, ",");
141 		for (n = 0;
142 		     n < sizeof(method_names) / sizeof(method_names[0]);
143 		     n++) {
144 			if (strncmp(p, method_names[n].name,
145 				    strlen(method_names[n].name)) == 0) {
146 				methods[n_methods++] = method_names[n].method;
147 			}
148 		}
149 		p = q + strspn(q, ",");
150 	}
151 
152 	/* Get the name of the local cell.  The root.afs volume which is
153 	 * mounted in /afs is mounted from the local cell, so we'll use that
154 	 * to determine which cell is considered the local cell.  Avoid getting
155 	 * tripped up by dynamic root support in clients. */
156 	memset(localcell, '\0', sizeof(localcell));
157 	if ((minikafs_ws_cell(localcell, sizeof(localcell) - 1) == 0) &&
158 	    (strcmp(localcell, "dynroot") != 0) &&
159 	    (!cell_is_in_option_list(options, localcell))) {
160 		if (options->debug) {
161 			debug("obtaining tokens for local cell '%s'",
162 			      localcell);
163 		}
164 		ret = minikafs_log(context, stash->v5ccache, options,
165 				   localcell, NULL, uid,
166 				   methods, n_methods);
167 		if (ret != 0) {
168 			if (stash->v5attempted != 0) {
169 				warn("got error %d (%s) while obtaining "
170 				     "tokens for %s",
171 				     ret, v5_error_message(ret), localcell);
172 			} else {
173 				if (options->debug) {
174 					debug("got error %d (%s) while "
175 					      "obtaining tokens for %s",
176 					      ret, v5_error_message(ret),
177 					      localcell);
178 				}
179 			}
180 		}
181 	}
182 	/* Get the name of the cell which houses the user's home directory.  In
183 	 * case intervening directories aren't readable by system:anyuser
184 	 * (which gives us an error), keep walking the directory chain until we
185 	 * either succeed or run out of path components to remove.  And try to
186 	 * avoid doing the same thing twice. */
187 	strncpy(homedir, info->homedir ? info->homedir : "/afs",
188 		sizeof(homedir) - 1);
189 	homedir[sizeof(homedir) - 1] = '\0';
190 	/* A common configuration is to have the home directory be a symlink
191 	 * into /afs.  If the homedir is a symlink, chase it, *once*. */
192 	if (lstat(homedir, &st) == 0) {
193 		if (st.st_mode & S_IFLNK) {
194 			/* Read the link. */
195 			memset(lnk, '\0', sizeof(lnk));
196 			if (readlink(homedir, lnk, sizeof(lnk) - 1) == 0) {
197 				/* If it's an absolute link, then we should
198 				 * ask about the link destination instead. */
199 				if ((strlen(lnk) > 0) && (lnk[0] == '/')) {
200 					strcpy(homedir, lnk);
201 				}
202 			}
203 		}
204 	}
205 	i = minikafs_cell_of_file_walk_up(homedir, homecell,
206 					  sizeof(homecell) - 1);
207 	if ((i == 0) &&
208 	    (strcmp(homecell, "dynroot") != 0) &&
209 	    (strcmp(homecell, localcell) != 0) &&
210 	    (!cell_is_in_option_list(options, homecell))) {
211 		if (options->debug) {
212 			debug("obtaining tokens for home cell '%s'", homecell);
213 		}
214 		ret = minikafs_log(context, stash->v5ccache, options,
215 				   homecell, NULL, uid,
216 				   methods, n_methods);
217 		if (ret != 0) {
218 			if (stash->v5attempted != 0) {
219 				warn("got error %d (%s) while obtaining "
220 				     "tokens for %s",
221 				     ret, v5_error_message(ret), homecell);
222 			} else {
223 				if (options->debug) {
224 					debug("got error %d (%s) while "
225 					      "obtaining tokens for %s",
226 					      ret, v5_error_message(ret),
227 					      homecell);
228 				}
229 			}
230 		}
231 	}
232 
233 	/* If there are no additional cells configured, stop here. */
234 	if (options->afs_cells == NULL) {
235 		if (options->debug) {
236 			debug("no additional afs cells configured");
237 		}
238 		return PAM_SUCCESS;
239 	}
240 
241 	/* Iterate through the list of other cells. */
242 	for (i = 0; i < options->n_afs_cells; i++) {
243 		if (options->debug) {
244 			if (options->afs_cells[i].principal_name != NULL) {
245 				debug("obtaining tokens for '%s' ('%s')",
246 				      options->afs_cells[i].cell,
247 				      options->afs_cells[i].principal_name);
248 			} else {
249 				debug("obtaining tokens for '%s'",
250 				      options->afs_cells[i].cell);
251 			}
252 		}
253 		ret = minikafs_log(context, stash->v5ccache, options,
254 				   options->afs_cells[i].cell,
255 				   options->afs_cells[i].principal_name, uid,
256 				   methods, n_methods);
257 		if (ret != 0) {
258 			if (stash->v5attempted != 0) {
259 				warn("got error %d (%s) while obtaining "
260 				     "tokens for %s",
261 				     ret, v5_error_message(ret),
262 				     options->afs_cells[i].cell);
263 			} else {
264 				if (options->debug) {
265 					debug("got error %d (%s) while "
266 					      "obtaining tokens for %s",
267 					      ret, v5_error_message(ret),
268 					      options->afs_cells[i].cell);
269 				}
270 			}
271 		}
272 	}
273 
274 	/* Suppress all errors. */
275 	return PAM_SUCCESS;
276 }
277 
278 int
tokens_release(struct _pam_krb5_stash * stash,struct _pam_krb5_options * options)279 tokens_release(struct _pam_krb5_stash *stash, struct _pam_krb5_options *options)
280 {
281 	struct stat st;
282 
283 	/* Check if AFS is running.  If it isn't, no other calls to libkrbafs
284 	 * will work, or even be safe to call. */
285 	if (!minikafs_has_afs()) {
286 		if (stat("/afs", &st) == 0) {
287 			warn("afs not running");
288 		} else {
289 			if (options->debug) {
290 				debug("afs not running");
291 			}
292 		}
293 		return PAM_SUCCESS;
294 	}
295 
296 	/* Destroy all tokens. */
297 	if (stash->afspag != 0) {
298 		if (options->debug) {
299 			debug("releasing afs tokens");
300 		}
301 		minikafs_unlog();
302 		stash->afspag = 0;
303 	}
304 
305 	/* Suppress all errors. */
306 	return PAM_SUCCESS;
307 }
308