Home | History | Annotate | Download | only in list
      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