1 /******************************************************************************
2 * A simple user-attribute based module for PAM.
3 *
4 * Copyright (c) 2003 Red Hat, Inc.
5 * Written by Nalin Dahyabhai <nalin@redhat.com>
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, and the entire permission notice in its entirety,
12 * including the disclaimer of warranties.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
16 * 3. The name of the author may not be used to endorse or promote
17 * products derived from this software without specific prior
18 * written permission.
19 *
20 * ALTERNATIVELY, this product may be distributed under the terms of
21 * the GNU Public License, in which case the provisions of the GPL are
22 * required INSTEAD OF the above restrictions. (This clause is
23 * necessary due to a potential bad interaction between the GPL and
24 * the restrictions contained in a BSD-style copyright.)
25 *
26 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
27 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
28 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
29 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
30 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
31 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
32 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
33 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
34 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
36 * OF THE POSSIBILITY OF SUCH DAMAGE.
37 */
38
39 #include "config.h"
40
41 #include <sys/types.h>
42 #include <errno.h>
43 #include <fcntl.h>
44 #include <fnmatch.h>
45 #include <limits.h>
46 #include <stdarg.h>
47 #include <stdio.h>
48 #include <stdlib.h>
49 #include <string.h>
50 #include <syslog.h>
51 #include <unistd.h>
52 #include <pwd.h>
53 #include <grp.h>
54 #include <netdb.h>
55
56 #include <security/pam_modules.h>
57 #include <security/pam_modutil.h>
58 #include <security/pam_ext.h>
59
60 /* Basically, run cmp(atol(left), atol(right)), returning PAM_SUCCESS if
61 * the function returns non-zero, PAM_AUTH_ERR if it returns zero, and
62 * PAM_SERVICE_ERR if the arguments can't be parsed as numbers. */
63 static int
64 evaluate_num(const pam_handle_t *pamh, const char *left,
65 const char *right, int (*cmp)(long long, long long))
66 {
67 long long l, r;
68 char *p;
69 int ret = PAM_SUCCESS;
70
71 errno = 0;
72 l = strtoll(left, &p, 0);
73 if ((p == NULL) || (*p != '\0') || errno) {
74 pam_syslog(pamh, LOG_INFO, "\"%s\" is not a number", left);
75 ret = PAM_SERVICE_ERR;
76 }
77
78 r = strtoll(right, &p, 0);
79 if ((p == NULL) || (*p != '\0') || errno) {
80 pam_syslog(pamh, LOG_INFO, "\"%s\" is not a number", right);
81 ret = PAM_SERVICE_ERR;
82 }
83
84 if (ret != PAM_SUCCESS) {
85 return ret;
86 }
87
88 return cmp(l, r) ? PAM_SUCCESS : PAM_AUTH_ERR;
89 }
90
91 /* Simple numeric comparison callbacks. */
92 static int
93 eq(long long i, long long j)
94 {
95 return i == j;
96 }
97 static int
98 ne(long long i, long long j)
99 {
100 return i != j;
101 }
102 static int
103 lt(long long i, long long j)
104 {
105 return i < j;
106 }
107 static int
108 le(long long i, long long j)
109 {
110 return lt(i, j) || eq(i, j);
111 }
112 static int
113 gt(long long i, long long j)
114 {
115 return i > j;
116 }
117 static int
118 ge(long long i, long long j)
119 {
120 return gt(i, j) || eq(i, j);
121 }
122
123 /* Test for numeric equality. */
124 static int
125 evaluate_eqn(const pam_handle_t *pamh, const char *left, const char *right)
126 {
127 return evaluate_num(pamh, left, right, eq);
128 }
129 /* Test for string equality. */
130 static int
131 evaluate_eqs(const char *left, const char *right)
132 {
133 return (strcmp(left, right) == 0) ? PAM_SUCCESS : PAM_AUTH_ERR;
134 }
135 /* Test for numeric inequality. */
136 static int
137 evaluate_nen(const pam_handle_t *pamh, const char *left, const char *right)
138 {
139 return evaluate_num(pamh, left, right, ne);
140 }
141 /* Test for string inequality. */
142 static int
143 evaluate_nes(const char *left, const char *right)
144 {
145 return (strcmp(left, right) != 0) ? PAM_SUCCESS : PAM_AUTH_ERR;
146 }
147 /* Test for numeric less-than-ness(?) */
148 static int
149 evaluate_lt(const pam_handle_t *pamh, const char *left, const char *right)
150 {
151 return evaluate_num(pamh, left, right, lt);
152 }
153 /* Test for numeric less-than-or-equal-ness(?) */
154 static int
155 evaluate_le(const pam_handle_t *pamh, const char *left, const char *right)
156 {
157 return evaluate_num(pamh, left, right, le);
158 }
159 /* Test for numeric greater-than-ness(?) */
160 static int
161 evaluate_gt(const pam_handle_t *pamh, const char *left, const char *right)
162 {
163 return evaluate_num(pamh, left, right, gt);
164 }
165 /* Test for numeric greater-than-or-equal-ness(?) */
166 static int
167 evaluate_ge(const pam_handle_t *pamh, const char *left, const char *right)
168 {
169 return evaluate_num(pamh, left, right, ge);
170 }
171 /* Check for file glob match. */
172 static int
173 evaluate_glob(const char *left, const char *right)
174 {
175 return (fnmatch(right, left, 0) == 0) ? PAM_SUCCESS : PAM_AUTH_ERR;
176 }
177 /* Check for file glob mismatch. */
178 static int
179 evaluate_noglob(const char *left, const char *right)
180 {
181 return (fnmatch(right, left, 0) != 0) ? PAM_SUCCESS : PAM_AUTH_ERR;
182 }
183 /* Check for list match. */
184 static int
185 evaluate_inlist(const char *left, const char *right)
186 {
187 char *p;
188 /* Don't care about left containing ':'. */
189 while ((p=strstr(right, left)) != NULL) {
190 if (p == right || *(p-1) == ':') { /* ':' is a list separator */
191 p += strlen(left);
192 if (*p == '\0' || *p == ':') {
193 return PAM_SUCCESS;
194 }
195 }
196 right = strchr(p, ':');
197 if (right == NULL)
198 break;
199 else
200 ++right;
201 }
202 return PAM_AUTH_ERR;
203 }
204 /* Check for list mismatch. */
205 static int
206 evaluate_notinlist(const char *left, const char *right)
207 {
208 return evaluate_inlist(left, right) != PAM_SUCCESS ? PAM_SUCCESS : PAM_AUTH_ERR;
209 }
210 /* Return PAM_SUCCESS if the user is in the group. */
211 static int
212 evaluate_ingroup(pam_handle_t *pamh, const char *user, const char *grouplist)
213 {
214 char *ptr = NULL;
215 static const char delim[] = ":";
216 char const *grp = NULL;
217 char *group = strdup(grouplist);
218
219 if (group == NULL)
220 return PAM_BUF_ERR;
221
222 grp = strtok_r(group, delim, &ptr);
223 while(grp != NULL) {
224 if (pam_modutil_user_in_group_nam_nam(pamh, user, grp) == 1) {
225 free(group);
226 return PAM_SUCCESS;
227 }
228 grp = strtok_r(NULL, delim, &ptr);
229 }
230 free(group);
231 return PAM_AUTH_ERR;
232 }
233 /* Return PAM_SUCCESS if the user is NOT in the group. */
234 static int
235 evaluate_notingroup(pam_handle_t *pamh, const char *user, const char *grouplist)
236 {
237 char *ptr = NULL;
238 static const char delim[] = ":";
239 char const *grp = NULL;
240 char *group = strdup(grouplist);
241
242 if (group == NULL)
243 return PAM_BUF_ERR;
244
245 grp = strtok_r(group, delim, &ptr);
246 while(grp != NULL) {
247 if (pam_modutil_user_in_group_nam_nam(pamh, user, grp) == 1) {
248 free(group);
249 return PAM_AUTH_ERR;
250 }
251 grp = strtok_r(NULL, delim, &ptr);
252 }
253 free(group);
254 return PAM_SUCCESS;
255 }
256
257 #ifdef HAVE_INNETGR
258 # define SOMETIMES_UNUSED UNUSED
259 #else
260 # define SOMETIMES_UNUSED
261 #endif
262
263 /* Return PAM_SUCCESS if the (host,user) is in the netgroup. */
264 static int
265 evaluate_innetgr(const pam_handle_t* pamh SOMETIMES_UNUSED, const char *host, const char *user, const char *group)
266 {
267 #ifdef HAVE_INNETGR
268 if (innetgr(group, host, user, NULL) == 1)
269 return PAM_SUCCESS;
270 #else
271 pam_syslog (pamh, LOG_ERR, "pam_succeed_if does not have netgroup support");
272 #endif
273
274 return PAM_AUTH_ERR;
275 }
276 /* Return PAM_SUCCESS if the (host,user) is NOT in the netgroup. */
277 static int
278 evaluate_notinnetgr(const pam_handle_t* pamh SOMETIMES_UNUSED, const char *host, const char *user, const char *group)
279 {
280 #ifdef HAVE_INNETGR
281 if (innetgr(group, host, user, NULL) == 0)
282 return PAM_SUCCESS;
283 #else
284 pam_syslog (pamh, LOG_ERR, "pam_succeed_if does not have netgroup support");
285 #endif
286 return PAM_AUTH_ERR;
287 }
288
289 /* Match a triple. */
290 static int
291 evaluate(pam_handle_t *pamh, int debug,
292 const char *left, const char *qual, const char *right,
293 struct passwd **pwd, const char *user)
294 {
295 char buf[LINE_MAX] = "";
296 const char *attribute = left;
297 /* Get information about the user if needed. */
298 if ((*pwd == NULL) &&
299 ((strcasecmp(left, "uid") == 0) ||
300 (strcasecmp(left, "gid") == 0) ||
301 (strcasecmp(left, "shell") == 0) ||
302 (strcasecmp(left, "home") == 0) ||
303 (strcasecmp(left, "dir") == 0) ||
304 (strcasecmp(left, "homedir") == 0))) {
305 *pwd = pam_modutil_getpwnam(pamh, user);
306 if (*pwd == NULL) {
307 return PAM_USER_UNKNOWN;
308 }
309 }
310 /* Figure out what we're evaluating here, and convert it to a string.*/
311 if ((strcasecmp(left, "login") == 0) ||
312 (strcasecmp(left, "name") == 0) ||
313 (strcasecmp(left, "user") == 0)) {
314 snprintf(buf, sizeof(buf), "%s", user);
315 left = buf;
316 } else if (strcasecmp(left, "uid") == 0) {
317 snprintf(buf, sizeof(buf), "%lu", (unsigned long) (*pwd)->pw_uid);
318 left = buf;
319 } else if (strcasecmp(left, "gid") == 0) {
320 snprintf(buf, sizeof(buf), "%lu", (unsigned long) (*pwd)->pw_gid);
321 left = buf;
322 } else if (strcasecmp(left, "shell") == 0) {
323 snprintf(buf, sizeof(buf), "%s", (*pwd)->pw_shell);
324 left = buf;
325 } else if ((strcasecmp(left, "home") == 0) ||
326 (strcasecmp(left, "dir") == 0) ||
327 (strcasecmp(left, "homedir") == 0)) {
328 snprintf(buf, sizeof(buf), "%s", (*pwd)->pw_dir);
329 left = buf;
330 } else if (strcasecmp(left, "service") == 0) {
331 const void *svc;
332 if (pam_get_item(pamh, PAM_SERVICE, &svc) != PAM_SUCCESS ||
333 svc == NULL)
334 svc = "";
335 snprintf(buf, sizeof(buf), "%s", (const char *)svc);
336 left = buf;
337 } else if (strcasecmp(left, "ruser") == 0) {
338 const void *ruser;
339 if (pam_get_item(pamh, PAM_RUSER, &ruser) != PAM_SUCCESS ||
340 ruser == NULL)
341 ruser = "";
342 snprintf(buf, sizeof(buf), "%s", (const char *)ruser);
343 left = buf;
344 user = buf;
345 } else if (strcasecmp(left, "rhost") == 0) {
346 const void *rhost;
347 if (pam_get_item(pamh, PAM_RHOST, &rhost) != PAM_SUCCESS ||
348 rhost == NULL)
349 rhost = "";
350 snprintf(buf, sizeof(buf), "%s", (const char *)rhost);
351 left = buf;
352 } else if (strcasecmp(left, "tty") == 0) {
353 const void *tty;
354 if (pam_get_item(pamh, PAM_TTY, &tty) != PAM_SUCCESS ||
355 tty == NULL)
356 tty = "";
357 snprintf(buf, sizeof(buf), "%s", (const char *)tty);
358 left = buf;
359 }
360 /* If we have no idea what's going on, return an error. */
361 if (left != buf) {
362 pam_syslog(pamh, LOG_ERR, "unknown attribute \"%s\"", left);
363 return PAM_SERVICE_ERR;
364 }
365 if (debug) {
366 pam_syslog(pamh, LOG_DEBUG, "'%s' resolves to '%s'",
367 attribute, left);
368 }
369
370 /* Attribute value < some threshold. */
371 if ((strcasecmp(qual, "<") == 0) ||
372 (strcasecmp(qual, "lt") == 0)) {
373 return evaluate_lt(pamh, left, right);
374 }
375 /* Attribute value <= some threshold. */
376 if ((strcasecmp(qual, "<=") == 0) ||
377 (strcasecmp(qual, "le") == 0)) {
378 return evaluate_le(pamh, left, right);
379 }
380 /* Attribute value > some threshold. */
381 if ((strcasecmp(qual, ">") == 0) ||
382 (strcasecmp(qual, "gt") == 0)) {
383 return evaluate_gt(pamh, left, right);
384 }
385 /* Attribute value >= some threshold. */
386 if ((strcasecmp(qual, ">=") == 0) ||
387 (strcasecmp(qual, "ge") == 0)) {
388 return evaluate_ge(pamh, left, right);
389 }
390 /* Attribute value == some threshold. */
391 if (strcasecmp(qual, "eq") == 0) {
392 return evaluate_eqn(pamh, left, right);
393 }
394 /* Attribute value = some string. */
395 if (strcasecmp(qual, "=") == 0) {
396 return evaluate_eqs(left, right);
397 }
398 /* Attribute value != some threshold. */
399 if (strcasecmp(qual, "ne") == 0) {
400 return evaluate_nen(pamh, left, right);
401 }
402 /* Attribute value != some string. */
403 if (strcasecmp(qual, "!=") == 0) {
404 return evaluate_nes(left, right);
405 }
406 /* Attribute value matches some pattern. */
407 if ((strcasecmp(qual, "=~") == 0) ||
408 (strcasecmp(qual, "glob") == 0)) {
409 return evaluate_glob(left, right);
410 }
411 if ((strcasecmp(qual, "!~") == 0) ||
412 (strcasecmp(qual, "noglob") == 0)) {
413 return evaluate_noglob(left, right);
414 }
415 /* Attribute value matches item in list. */
416 if (strcasecmp(qual, "in") == 0) {
417 return evaluate_inlist(left, right);
418 }
419 if (strcasecmp(qual, "notin") == 0) {
420 return evaluate_notinlist(left, right);
421 }
422 /* User is in this group(s). */
423 if (strcasecmp(qual, "ingroup") == 0) {
424 return evaluate_ingroup(pamh, user, right);
425 }
426 /* User is not in this group(s). */
427 if (strcasecmp(qual, "notingroup") == 0) {
428 return evaluate_notingroup(pamh, user, right);
429 }
430 /* (Rhost, user) is in this netgroup. */
431 if (strcasecmp(qual, "innetgr") == 0) {
432 const void *rhost;
433 if (pam_get_item(pamh, PAM_RHOST, &rhost) != PAM_SUCCESS)
434 rhost = NULL;
435 return evaluate_innetgr(pamh, rhost, user, right);
436 }
437 /* (Rhost, user) is not in this group. */
438 if (strcasecmp(qual, "notinnetgr") == 0) {
439 const void *rhost;
440 if (pam_get_item(pamh, PAM_RHOST, &rhost) != PAM_SUCCESS)
441 rhost = NULL;
442 return evaluate_notinnetgr(pamh, rhost, user, right);
443 }
444 /* Fail closed. */
445 return PAM_SERVICE_ERR;
446 }
447
448 int
449 pam_sm_authenticate (pam_handle_t *pamh, int flags UNUSED,
450 int argc, const char **argv)
451 {
452 const char *user;
453 struct passwd *pwd = NULL;
454 int ret, i, count, use_uid, debug;
455 const char *left, *right, *qual;
456 int quiet_fail, quiet_succ, audit;
457
458 quiet_fail = 0;
459 quiet_succ = 0;
460 audit = 0;
461 for (use_uid = 0, debug = 0, i = 0; i < argc; i++) {
462 if (strcmp(argv[i], "debug") == 0) {
463 debug++;
464 }
465 if (strcmp(argv[i], "use_uid") == 0) {
466 use_uid++;
467 }
468 if (strcmp(argv[i], "quiet") == 0) {
469 quiet_fail++;
470 quiet_succ++;
471 }
472 if (strcmp(argv[i], "quiet_fail") == 0) {
473 quiet_fail++;
474 }
475 if (strcmp(argv[i], "quiet_success") == 0) {
476 quiet_succ++;
477 }
478 if (strcmp(argv[i], "audit") == 0) {
479 audit++;
480 }
481 }
482
483 if (use_uid) {
484 /* Get information about the user. */
485 pwd = pam_modutil_getpwuid(pamh, getuid());
486 if (pwd == NULL) {
487 pam_syslog(pamh, LOG_ERR,
488 "error retrieving information about user %lu",
489 (unsigned long)getuid());
490 return PAM_USER_UNKNOWN;
491 }
492 user = pwd->pw_name;
493 } else {
494 /* Get the user's name. */
495 ret = pam_get_user(pamh, &user, NULL);
496 if (ret != PAM_SUCCESS) {
497 pam_syslog(pamh, LOG_NOTICE,
498 "cannot determine user name: %s",
499 pam_strerror(pamh, ret));
500 return ret;
501 }
502
503 /* Postpone requesting password data until it is needed */
504 }
505
506 /* Walk the argument list. */
507 count = 0;
508 left = qual = right = NULL;
509 for (i = 0; i < argc; i++) {
510 if (strcmp(argv[i], "debug") == 0) {
511 continue;
512 }
513 if (strcmp(argv[i], "use_uid") == 0) {
514 continue;
515 }
516 if (strcmp(argv[i], "quiet") == 0) {
517 continue;
518 }
519 if (strcmp(argv[i], "quiet_fail") == 0) {
520 continue;
521 }
522 if (strcmp(argv[i], "quiet_success") == 0) {
523 continue;
524 }
525 if (strcmp(argv[i], "audit") == 0) {
526 continue;
527 }
528 if (left == NULL) {
529 left = argv[i];
530 continue;
531 }
532 if (qual == NULL) {
533 qual = argv[i];
534 continue;
535 }
536 if (right == NULL) {
537 right = argv[i];
538 if (right == NULL)
539 continue;
540
541 count++;
542 ret = evaluate(pamh, debug,
543 left, qual, right,
544 &pwd, user);
545 if (ret == PAM_USER_UNKNOWN && audit)
546 pam_syslog(pamh, LOG_NOTICE,
547 "error retrieving information about user %s",
548 user);
549 if (ret != PAM_SUCCESS) {
550 if(!quiet_fail && ret != PAM_USER_UNKNOWN)
551 pam_syslog(pamh, LOG_INFO,
552 "requirement \"%s %s %s\" "
553 "not met by user \"%s\"",
554 left, qual, right, user);
555 left = qual = right = NULL;
556 break;
557 }
558 else
559 if(!quiet_succ)
560 pam_syslog(pamh, LOG_INFO,
561 "requirement \"%s %s %s\" "
562 "was met by user \"%s\"",
563 left, qual, right, user);
564 left = qual = right = NULL;
565 continue;
566 }
567 }
568
569 if (left || qual || right) {
570 ret = PAM_SERVICE_ERR;
571 pam_syslog(pamh, LOG_ERR,
572 "incomplete condition detected");
573 } else if (count == 0) {
574 pam_syslog(pamh, LOG_INFO,
575 "no condition detected; module succeeded");
576 }
577
578 return ret;
579 }
580
581 int
582 pam_sm_setcred(pam_handle_t *pamh UNUSED, int flags UNUSED,
583 int argc UNUSED, const char **argv UNUSED)
584 {
585 return PAM_IGNORE;
586 }
587
588 int
589 pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, const char **argv)
590 {
591 return pam_sm_authenticate(pamh, flags, argc, argv);
592 }
593
594 int
595 pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, const char **argv)
596 {
597 return pam_sm_authenticate(pamh, flags, argc, argv);
598 }
599
600 int
601 pam_sm_close_session(pam_handle_t *pamh, int flags, int argc, const char **argv)
602 {
603 return pam_sm_authenticate(pamh, flags, argc, argv);
604 }
605
606 int
607 pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, const char **argv)
608 {
609 return pam_sm_authenticate(pamh, flags, argc, argv);
610 }