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 *
string_mode_type(pam_list_mode_t op_mode,boolean_t allow)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
log_illegal_combination(const char * s1,const char * s2)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
pam_sm_acct_mgmt(pam_handle_t * pamh,int flags,int argc,const char ** argv)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