xref: /illumos-gate/usr/src/lib/pam_modules/list/list.c (revision 581cede61ac9c14d8d4ea452562a567189eead78)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #include <stdio.h>
28 #include <string.h>
29 #include <sys/types.h>
30 #include <sys/stat.h>
31 #include <syslog.h>
32 #include <netdb.h>
33 #include <malloc.h>
34 #include <unistd.h>
35 #include <errno.h>
36 #include <security/pam_appl.h>
37 #include <security/pam_modules.h>
38 #include <security/pam_impl.h>
39 
40 #define	ILLEGAL_COMBINATION "pam_list: illegal combination of options"
41 
42 typedef enum {
43 	LIST_EXTERNAL_FILE,
44 	LIST_PLUS_CHECK,
45 	LIST_COMPAT_MODE
46 } pam_list_mode_t;
47 
48 static const char *
49 string_mode_type(pam_list_mode_t op_mode, boolean_t allow)
50 {
51 	return ((op_mode == LIST_COMPAT_MODE) ? "compat" :
52 	    (allow ? "allow" : "deny"));
53 }
54 
55 static void
56 log_illegal_combination(const char *s1, const char *s2)
57 {
58 	__pam_log(LOG_AUTH | LOG_ERR, ILLEGAL_COMBINATION
59 	    " %s and %s", s1, s2);
60 }
61 
62 /*ARGSUSED*/
63 int
64 pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, const char **argv)
65 {
66 	FILE	*fd;
67 	const char	*allowdeny_filename = PF_PATH;
68 	char	buf[BUFSIZ];
69 	char	hostname[MAXHOSTNAMELEN];
70 	char	*username = NULL;
71 	char	*bufp;
72 	char	*rhost;
73 	char 	*limit;
74 	int	userok = 0;
75 	int	hostok = 0;
76 	int	i;
77 	int	allow_deny_test = 0;
78 	boolean_t	debug = B_FALSE;
79 	boolean_t	allow = B_FALSE;
80 	boolean_t	matched = B_FALSE;
81 	boolean_t	check_user = B_TRUE;
82 	boolean_t	check_host = B_FALSE;
83 	boolean_t	check_exact = B_FALSE;
84 	pam_list_mode_t	op_mode = LIST_PLUS_CHECK;
85 
86 	for (i = 0; i < argc; ++i) {
87 		if (strncasecmp(argv[i], "debug", sizeof ("debug")) == 0) {
88 			debug = B_TRUE;
89 		} else if (strncasecmp(argv[i], "user", sizeof ("user")) == 0) {
90 			check_user = B_TRUE;
91 		} else if (strncasecmp(argv[i], "nouser",
92 		    sizeof ("nouser")) == 0) {
93 			check_user = B_FALSE;
94 		} else if (strncasecmp(argv[i], "host", sizeof ("host")) == 0) {
95 			check_host = B_TRUE;
96 		} else if (strncasecmp(argv[i], "nohost",
97 		    sizeof ("nohost")) == 0) {
98 			check_host = B_FALSE;
99 		} else if (strncasecmp(argv[i], "user_host_exact",
100 		    sizeof ("user_host_exact")) == 0) {
101 			check_exact = B_TRUE;
102 		} else if (strcasecmp(argv[i], "compat") == 0) {
103 			if (op_mode == LIST_PLUS_CHECK) {
104 				op_mode = LIST_COMPAT_MODE;
105 			} else {
106 				log_illegal_combination("compat",
107 				    string_mode_type(op_mode, allow));
108 				return (PAM_SERVICE_ERR);
109 			}
110 		} else if (strncasecmp(argv[i], "allow=",
111 		    sizeof ("allow=") - 1) == 0) {
112 			if (op_mode == LIST_PLUS_CHECK) {
113 				allowdeny_filename = argv[i] +
114 				    sizeof ("allow=") - 1;
115 				allow = B_TRUE;
116 				op_mode = LIST_EXTERNAL_FILE;
117 				allow_deny_test++;
118 			} else {
119 				log_illegal_combination("allow",
120 				    string_mode_type(op_mode, allow));
121 				return (PAM_SERVICE_ERR);
122 			}
123 		} else if (strncasecmp(argv[i], "deny=",
124 		    sizeof ("deny=") - 1) == 0) {
125 			if (op_mode == LIST_PLUS_CHECK) {
126 				allowdeny_filename = argv[i] +
127 				    sizeof ("deny=") - 1;
128 				allow = B_FALSE;
129 				op_mode = LIST_EXTERNAL_FILE;
130 				allow_deny_test++;
131 			} else {
132 				log_illegal_combination("deny",
133 				    string_mode_type(op_mode, allow));
134 				return (PAM_SERVICE_ERR);
135 			}
136 		} else {
137 			__pam_log(LOG_AUTH | LOG_ERR,
138 			    "pam_list: illegal option %s", argv[i]);
139 			return (PAM_SERVICE_ERR);
140 		}
141 	}
142 
143 	if (((check_user || check_host || check_exact) == B_FALSE) ||
144 	    (allow_deny_test > 1)) {
145 		__pam_log(LOG_AUTH | LOG_ERR, ILLEGAL_COMBINATION);
146 		return (PAM_SERVICE_ERR);
147 	}
148 
149 	if ((op_mode == LIST_COMPAT_MODE) && (check_user == B_FALSE)) {
150 		log_illegal_combination("compat", "nouser");
151 		return (PAM_SERVICE_ERR);
152 	}
153 
154 	if (debug) {
155 		__pam_log(LOG_AUTH | LOG_DEBUG,
156 		    "pam_list: check_user = %d, check_host = %d,"
157 		    "check_exact = %d\n",
158 		    check_user, check_host, check_exact);
159 
160 		__pam_log(LOG_AUTH | LOG_DEBUG,
161 		    "pam_list: auth_file: %s, %s\n", allowdeny_filename,
162 		    (op_mode == LIST_COMPAT_MODE) ? "compat mode" :
163 		    (allow ? "allow file" : "deny file"));
164 	}
165 
166 	(void) pam_get_item(pamh, PAM_USER, (void**)&username);
167 
168 	if ((check_user || check_exact) && ((username == NULL) ||
169 	    (*username == '\0'))) {
170 		__pam_log(LOG_AUTH | LOG_ERR,
171 		    "pam_list: username not supplied, critical error");
172 		return (PAM_USER_UNKNOWN);
173 	}
174 
175 	(void) pam_get_item(pamh, PAM_RHOST, (void**)&rhost);
176 
177 	if ((check_host || check_exact) && ((rhost == NULL) ||
178 	    (*rhost == '\0'))) {
179 		if (gethostname(hostname, MAXHOSTNAMELEN) == 0) {
180 			rhost = hostname;
181 		} else {
182 			__pam_log(LOG_AUTH | LOG_ERR,
183 			    "pam_list: error by gethostname - %m");
184 			return (PAM_SERVICE_ERR);
185 		}
186 	}
187 
188 	if (debug) {
189 		__pam_log(LOG_AUTH | LOG_DEBUG,
190 		    "pam_list: pam_sm_acct_mgmt for (%s,%s,)",
191 		    (rhost != NULL) ? rhost : "", username);
192 	}
193 
194 	if (strlen(allowdeny_filename) == 0) {
195 		__pam_log(LOG_AUTH | LOG_ERR,
196 		    "pam_list: file name not specified");
197 		return (PAM_SERVICE_ERR);
198 	}
199 
200 	if ((fd = fopen(allowdeny_filename, "rF")) == NULL) {
201 		__pam_log(LOG_AUTH | LOG_ERR, "pam_list: fopen of %s: %s",
202 		    allowdeny_filename, strerror(errno));
203 		return (PAM_SERVICE_ERR);
204 	}
205 
206 	while (fgets(buf, BUFSIZ, fd) != NULL) {
207 		/* lines longer than BUFSIZ-1 */
208 		if ((strlen(buf) == (BUFSIZ - 1)) &&
209 		    (buf[BUFSIZ - 2] != '\n')) {
210 			while ((fgetc(fd) != '\n') && (!feof(fd))) {
211 				continue;
212 			}
213 			__pam_log(LOG_AUTH | LOG_DEBUG,
214 			    "pam_list: long line in file,"
215 			    "more than %d chars, the rest ignored", BUFSIZ - 1);
216 		}
217 
218 		/* remove unneeded colons if necessary */
219 		if ((limit = strpbrk(buf, ":\n")) != NULL) {
220 			*limit = '\0';
221 		}
222 
223 		/* ignore free values */
224 		if (buf[0] == '\0') {
225 			continue;
226 		}
227 
228 		bufp = buf;
229 
230 		/* test for interesting lines = +/- in /etc/passwd */
231 		if (op_mode == LIST_COMPAT_MODE) {
232 			/* simple + matches all */
233 			if ((buf[0] == '+') && (buf[1] == '\0')) {
234 				matched = B_TRUE;
235 				allow = B_TRUE;
236 				break;
237 			}
238 
239 			/* simple - is not defined */
240 			if ((buf[0] == '-') && (buf[1] == '\0')) {
241 				__pam_log(LOG_AUTH | LOG_ERR,
242 				    "pam_list: simple minus unknown, "
243 				    "illegal line in " PF_PATH);
244 				(void) fclose(fd);
245 				return (PAM_SERVICE_ERR);
246 			}
247 
248 			/* @ is not allowed on the first position */
249 			if (buf[0] == '@') {
250 				__pam_log(LOG_AUTH | LOG_ERR,
251 				    "pam_list: @ is not allowed on the first "
252 				    "position in " PF_PATH);
253 				(void) fclose(fd);
254 				return (PAM_SERVICE_ERR);
255 			}
256 
257 			/* -user or -@netgroup */
258 			if (buf[0] == '-') {
259 				allow = B_FALSE;
260 				bufp++;
261 			/* +user or +@netgroup */
262 			} else if (buf[0] == '+') {
263 				allow = B_TRUE;
264 				bufp++;
265 			/* user */
266 			} else {
267 				allow = B_TRUE;
268 			}
269 		} else if (op_mode == LIST_PLUS_CHECK) {
270 			if (((buf[0] != '+') && (buf[0] != '-')) ||
271 			    (buf[1] == '\0')) {
272 				continue;
273 			}
274 
275 			if (buf[0] == '+') {
276 				allow = B_TRUE;
277 			} else {
278 				allow = B_FALSE;
279 			}
280 			bufp++;
281 		}
282 
283 		/*
284 		 * if -> netgroup line
285 		 * else -> user line
286 		 */
287 		if ((bufp[0] == '@') && (bufp[1] != '\0')) {
288 			bufp++;
289 
290 			if (check_exact) {
291 				if (innetgr(bufp, rhost, username,
292 				    NULL) == 1) {
293 					matched = B_TRUE;
294 					break;
295 				}
296 			} else {
297 				if (check_user) {
298 					userok = innetgr(bufp, NULL, username,
299 					    NULL);
300 				} else {
301 					userok = 1;
302 				}
303 				if (check_host) {
304 					hostok = innetgr(bufp, rhost, NULL,
305 					    NULL);
306 				} else {
307 					hostok = 1;
308 				}
309 				if (userok && hostok) {
310 					matched = B_TRUE;
311 					break;
312 				}
313 			}
314 		} else {
315 			if (check_user) {
316 				if (strcmp(bufp, username) == 0) {
317 					matched = B_TRUE;
318 					break;
319 				}
320 			}
321 		}
322 
323 		/*
324 		 * No match found in /etc/passwd yet.  For compat mode
325 		 * a failure to match should result in a return of
326 		 * PAM_PERM_DENIED which is achieved below if 'matched'
327 		 * is false and 'allow' is true.
328 		 */
329 		if (op_mode == LIST_COMPAT_MODE) {
330 			allow = B_TRUE;
331 		}
332 	}
333 	(void) fclose(fd);
334 
335 	if (debug) {
336 		__pam_log(LOG_AUTH | LOG_DEBUG,
337 		    "pam_list: %s for %s", matched ? "matched" : "no match",
338 		    allow ? "allow" : "deny");
339 	}
340 
341 	if (matched) {
342 		return (allow ? PAM_SUCCESS : PAM_PERM_DENIED);
343 	}
344 	/*
345 	 * For compatibility with passwd_compat mode to prevent root access
346 	 * denied.
347 	 */
348 	if (op_mode == LIST_PLUS_CHECK) {
349 		return (PAM_IGNORE);
350 	}
351 	return (allow ? PAM_PERM_DENIED : PAM_SUCCESS);
352 }
353