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 <grp.h>
37 #include <security/pam_appl.h>
38 #include <security/pam_modules.h>
39 #include <security/pam_impl.h>
40
41 #define ILLEGAL_COMBINATION "pam_list: illegal combination of options"
42
43 typedef enum {
44 LIST_EXTERNAL_FILE,
45 LIST_PLUS_CHECK,
46 LIST_COMPAT_MODE
47 } pam_list_mode_t;
48
49 static const char *
string_mode_type(pam_list_mode_t op_mode,boolean_t allow)50 string_mode_type(pam_list_mode_t op_mode, boolean_t allow)
51 {
52 return ((op_mode == LIST_COMPAT_MODE) ? "compat" :
53 (allow ? "allow" : "deny"));
54 }
55
56 static void
log_illegal_combination(const char * s1,const char * s2)57 log_illegal_combination(const char *s1, const char *s2)
58 {
59 __pam_log(LOG_AUTH | LOG_ERR, ILLEGAL_COMBINATION
60 " %s and %s", s1, s2);
61 }
62
63 /*ARGSUSED*/
64 int
pam_sm_acct_mgmt(pam_handle_t * pamh,int flags,int argc,const char ** argv)65 pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, const char **argv)
66 {
67 FILE *fd;
68 const char *allowdeny_filename = PF_PATH;
69 char buf[BUFSIZ];
70 char hostname[MAXHOSTNAMELEN];
71 char *username = NULL;
72 char *grbuf = NULL;
73 char *bufp;
74 char *rhost;
75 char *limit;
76 int userok = 0;
77 int hostok = 0;
78 int i;
79 int allow_deny_test = 0;
80 long grbuflen = 0;
81 boolean_t debug = B_FALSE;
82 boolean_t allow = B_FALSE;
83 boolean_t matched = B_FALSE;
84 boolean_t check_user = B_TRUE;
85 boolean_t check_group = B_FALSE;
86 boolean_t check_host = B_FALSE;
87 boolean_t check_exact = B_FALSE;
88 pam_list_mode_t op_mode = LIST_PLUS_CHECK;
89
90 // group reentrant interfaces limits
91 if ((grbuflen = sysconf(_SC_GETGR_R_SIZE_MAX)) <= 0)
92 return (PAM_BUF_ERR);
93
94 for (i = 0; i < argc; ++i) {
95 if (strncasecmp(argv[i], "debug", sizeof ("debug")) == 0) {
96 debug = B_TRUE;
97 } else if (strncasecmp(argv[i], "group",
98 sizeof ("group")) == 0) {
99 check_group = B_TRUE;
100 } else if (strncasecmp(argv[i], "user", sizeof ("user")) == 0) {
101 check_user = B_TRUE;
102 } else if (strncasecmp(argv[i], "nouser",
103 sizeof ("nouser")) == 0) {
104 check_user = B_FALSE;
105 } else if (strncasecmp(argv[i], "host", sizeof ("host")) == 0) {
106 check_host = B_TRUE;
107 } else if (strncasecmp(argv[i], "nohost",
108 sizeof ("nohost")) == 0) {
109 check_host = B_FALSE;
110 } else if (strncasecmp(argv[i], "user_host_exact",
111 sizeof ("user_host_exact")) == 0) {
112 check_exact = B_TRUE;
113 } else if (strcasecmp(argv[i], "compat") == 0) {
114 if (op_mode == LIST_PLUS_CHECK) {
115 op_mode = LIST_COMPAT_MODE;
116 } else {
117 log_illegal_combination("compat",
118 string_mode_type(op_mode, allow));
119 return (PAM_SERVICE_ERR);
120 }
121 } else if (strncasecmp(argv[i], "allow=",
122 sizeof ("allow=") - 1) == 0) {
123 if (op_mode == LIST_PLUS_CHECK) {
124 allowdeny_filename = argv[i] +
125 sizeof ("allow=") - 1;
126 allow = B_TRUE;
127 op_mode = LIST_EXTERNAL_FILE;
128 allow_deny_test++;
129 } else {
130 log_illegal_combination("allow",
131 string_mode_type(op_mode, allow));
132 return (PAM_SERVICE_ERR);
133 }
134 } else if (strncasecmp(argv[i], "deny=",
135 sizeof ("deny=") - 1) == 0) {
136 if (op_mode == LIST_PLUS_CHECK) {
137 allowdeny_filename = argv[i] +
138 sizeof ("deny=") - 1;
139 allow = B_FALSE;
140 op_mode = LIST_EXTERNAL_FILE;
141 allow_deny_test++;
142 } else {
143 log_illegal_combination("deny",
144 string_mode_type(op_mode, allow));
145 return (PAM_SERVICE_ERR);
146 }
147 } else {
148 __pam_log(LOG_AUTH | LOG_ERR,
149 "pam_list: illegal option %s", argv[i]);
150 return (PAM_SERVICE_ERR);
151 }
152 }
153
154 if (((check_user || check_group || check_host ||
155 check_exact) == B_FALSE) || (allow_deny_test > 1)) {
156 __pam_log(LOG_AUTH | LOG_ERR, ILLEGAL_COMBINATION);
157 return (PAM_SERVICE_ERR);
158 }
159
160 if ((op_mode == LIST_COMPAT_MODE) && (check_user == B_FALSE)) {
161 log_illegal_combination("compat", "nouser");
162 return (PAM_SERVICE_ERR);
163 }
164
165 if ((op_mode == LIST_COMPAT_MODE) && (check_group == B_TRUE)) {
166 log_illegal_combination("compat", "group");
167 return (PAM_SERVICE_ERR);
168 }
169
170 if (debug) {
171 __pam_log(LOG_AUTH | LOG_DEBUG,
172 "pam_list: check_user = %d, check_host = %d,"
173 "check_exact = %d\n",
174 check_user, check_host, check_exact);
175
176 __pam_log(LOG_AUTH | LOG_DEBUG,
177 "pam_list: auth_file: %s, %s\n", allowdeny_filename,
178 (op_mode == LIST_COMPAT_MODE) ? "compat mode" :
179 (allow ? "allow file" : "deny file"));
180 }
181
182 (void) pam_get_item(pamh, PAM_USER, (void**)&username);
183
184 if ((check_user || check_group || check_exact) && ((username == NULL) ||
185 (*username == '\0'))) {
186 __pam_log(LOG_AUTH | LOG_ERR,
187 "pam_list: username not supplied, critical error");
188 return (PAM_USER_UNKNOWN);
189 }
190
191 (void) pam_get_item(pamh, PAM_RHOST, (void**)&rhost);
192
193 if ((check_host || check_exact) && ((rhost == NULL) ||
194 (*rhost == '\0'))) {
195 if (gethostname(hostname, MAXHOSTNAMELEN) == 0) {
196 rhost = hostname;
197 } else {
198 __pam_log(LOG_AUTH | LOG_ERR,
199 "pam_list: error by gethostname - %m");
200 return (PAM_SERVICE_ERR);
201 }
202 }
203
204 if (debug) {
205 __pam_log(LOG_AUTH | LOG_DEBUG,
206 "pam_list: pam_sm_acct_mgmt for (%s,%s,)",
207 (rhost != NULL) ? rhost : "", username);
208 }
209
210 if (strlen(allowdeny_filename) == 0) {
211 __pam_log(LOG_AUTH | LOG_ERR,
212 "pam_list: file name not specified");
213 return (PAM_SERVICE_ERR);
214 }
215
216 if ((fd = fopen(allowdeny_filename, "rF")) == NULL) {
217 __pam_log(LOG_AUTH | LOG_ERR, "pam_list: fopen of %s: %s",
218 allowdeny_filename, strerror(errno));
219 return (PAM_SERVICE_ERR);
220 }
221
222 if (check_group && ((grbuf = calloc(1, grbuflen)) == NULL)) {
223 __pam_log(LOG_AUTH | LOG_ERR,
224 "pam_list: could not allocate memory for group");
225 return (PAM_BUF_ERR);
226 }
227
228 while (fgets(buf, BUFSIZ, fd) != NULL) {
229 /* lines longer than BUFSIZ-1 */
230 if ((strlen(buf) == (BUFSIZ - 1)) &&
231 (buf[BUFSIZ - 2] != '\n')) {
232 while ((fgetc(fd) != '\n') && (!feof(fd))) {
233 continue;
234 }
235 __pam_log(LOG_AUTH | LOG_DEBUG,
236 "pam_list: long line in file,"
237 "more than %d chars, the rest ignored", BUFSIZ - 1);
238 }
239
240 /* remove unneeded colons if necessary */
241 if ((limit = strpbrk(buf, ":\n")) != NULL) {
242 *limit = '\0';
243 }
244
245 /* ignore free values */
246 if (buf[0] == '\0') {
247 continue;
248 }
249
250 bufp = buf;
251
252 /* test for interesting lines = +/- in /etc/passwd */
253 if (op_mode == LIST_COMPAT_MODE) {
254 /* simple + matches all */
255 if ((buf[0] == '+') && (buf[1] == '\0')) {
256 matched = B_TRUE;
257 allow = B_TRUE;
258 break;
259 }
260
261 /* simple - is not defined */
262 if ((buf[0] == '-') && (buf[1] == '\0')) {
263 __pam_log(LOG_AUTH | LOG_ERR,
264 "pam_list: simple minus unknown, "
265 "illegal line in " PF_PATH);
266 (void) fclose(fd);
267 free(grbuf);
268 return (PAM_SERVICE_ERR);
269 }
270
271 /* @ is not allowed on the first position */
272 if (buf[0] == '@') {
273 __pam_log(LOG_AUTH | LOG_ERR,
274 "pam_list: @ is not allowed on the first "
275 "position in " PF_PATH);
276 (void) fclose(fd);
277 free(grbuf);
278 return (PAM_SERVICE_ERR);
279 }
280
281 /* -user or -@netgroup */
282 if (buf[0] == '-') {
283 allow = B_FALSE;
284 bufp++;
285 /* +user or +@netgroup */
286 } else if (buf[0] == '+') {
287 allow = B_TRUE;
288 bufp++;
289 /* user */
290 } else {
291 allow = B_TRUE;
292 }
293 } else if (op_mode == LIST_PLUS_CHECK) {
294 if (((buf[0] != '+') && (buf[0] != '-')) ||
295 (buf[1] == '\0')) {
296 continue;
297 }
298
299 if (buf[0] == '+') {
300 allow = B_TRUE;
301 } else {
302 allow = B_FALSE;
303 }
304 bufp++;
305 }
306
307 /*
308 * if -> netgroup line
309 * else if -> group line
310 * else -> user line
311 */
312 if ((bufp[0] == '@') && (bufp[1] != '\0')) {
313 bufp++;
314
315 if (check_exact) {
316 if (innetgr(bufp, rhost, username,
317 NULL) == 1) {
318 matched = B_TRUE;
319 break;
320 }
321 } else {
322 if (check_user) {
323 userok = innetgr(bufp, NULL, username,
324 NULL);
325 } else {
326 userok = 1;
327 }
328 if (check_host) {
329 hostok = innetgr(bufp, rhost, NULL,
330 NULL);
331 } else {
332 hostok = 1;
333 }
334 if (userok && hostok) {
335 matched = B_TRUE;
336 break;
337 }
338 }
339 } else if ((bufp[0] == '%') && (bufp[1] != '\0')) {
340 char **member;
341 struct group grp;
342
343 if (check_group == B_FALSE)
344 continue;
345
346 bufp++;
347
348 if (getgrnam_r(bufp, &grp, grbuf, grbuflen) != NULL) {
349 for (member = grp.gr_mem; *member != NULL;
350 member++) {
351 if (strcmp(*member, username) == 0) {
352 matched = B_TRUE;
353 break;
354 }
355 }
356 } else {
357 __pam_log(LOG_AUTH | LOG_ERR,
358 "pam_list: %s is not a known group",
359 bufp);
360 }
361 } else {
362 if (check_user) {
363 if (strcmp(bufp, username) == 0) {
364 matched = B_TRUE;
365 break;
366 }
367 }
368 }
369
370 /*
371 * No match found in /etc/passwd yet. For compat mode
372 * a failure to match should result in a return of
373 * PAM_PERM_DENIED which is achieved below if 'matched'
374 * is false and 'allow' is true.
375 */
376 if (op_mode == LIST_COMPAT_MODE) {
377 allow = B_TRUE;
378 }
379 }
380 (void) fclose(fd);
381 free(grbuf);
382
383 if (debug) {
384 __pam_log(LOG_AUTH | LOG_DEBUG,
385 "pam_list: %s for %s", matched ? "matched" : "no match",
386 allow ? "allow" : "deny");
387 }
388
389 if (matched) {
390 return (allow ? PAM_SUCCESS : PAM_PERM_DENIED);
391 }
392 /*
393 * For compatibility with passwd_compat mode to prevent root access
394 * denied.
395 */
396 if (op_mode == LIST_PLUS_CHECK) {
397 return (PAM_IGNORE);
398 }
399 return (allow ? PAM_PERM_DENIED : PAM_SUCCESS);
400 }
401