openpam_configure.c revision 255369
1/*- 2 * Copyright (c) 2001-2003 Networks Associates Technology, Inc. 3 * Copyright (c) 2004-2012 Dag-Erling Sm��rgrav 4 * All rights reserved. 5 * 6 * This software was developed for the FreeBSD Project by ThinkSec AS and 7 * Network Associates Laboratories, the Security Research Division of 8 * Network Associates, Inc. under DARPA/SPAWAR contract N66001-01-C-8035 9 * ("CBOSS"), as part of the DARPA CHATS research program. 10 * 11 * Redistribution and use in source and binary forms, with or without 12 * modification, are permitted provided that the following conditions 13 * are met: 14 * 1. Redistributions of source code must retain the above copyright 15 * notice, this list of conditions and the following disclaimer. 16 * 2. Redistributions in binary form must reproduce the above copyright 17 * notice, this list of conditions and the following disclaimer in the 18 * documentation and/or other materials provided with the distribution. 19 * 3. The name of the author may not be used to endorse or promote 20 * products derived from this software without specific prior written 21 * permission. 22 * 23 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 26 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 33 * SUCH DAMAGE. 34 * 35 * $Id: openpam_configure.c 612 2012-05-26 23:02:55Z des $ 36 */ 37 38#ifdef HAVE_CONFIG_H 39# include "config.h" 40#endif 41 42#include <sys/param.h> 43 44#include <ctype.h> 45#include <errno.h> 46#include <stdio.h> 47#include <stdlib.h> 48#include <string.h> 49 50#include <security/pam_appl.h> 51 52#include "openpam_impl.h" 53#include "openpam_ctype.h" 54#include "openpam_strlcat.h" 55#include "openpam_strlcpy.h" 56 57static int openpam_load_chain(pam_handle_t *, const char *, pam_facility_t); 58 59/* 60 * Validate a service name. 61 * 62 * Returns a non-zero value if the argument points to a NUL-terminated 63 * string consisting entirely of characters in the POSIX portable filename 64 * character set, excluding the path separator character. 65 */ 66static int 67valid_service_name(const char *name) 68{ 69 const char *p; 70 71 if (OPENPAM_FEATURE(RESTRICT_SERVICE_NAME)) { 72 /* path separator not allowed */ 73 for (p = name; *p != '\0'; ++p) 74 if (!is_pfcs(*p)) 75 return (0); 76 } else { 77 /* path separator allowed */ 78 for (p = name; *p != '\0'; ++p) 79 if (!is_pfcs(*p) && *p != '/') 80 return (0); 81 } 82 return (1); 83} 84 85/* 86 * Parse the facility name. 87 * 88 * Returns the corresponding pam_facility_t value, or -1 if the argument 89 * is not a valid facility name. 90 */ 91static pam_facility_t 92parse_facility_name(const char *name) 93{ 94 int i; 95 96 for (i = 0; i < PAM_NUM_FACILITIES; ++i) 97 if (strcmp(pam_facility_name[i], name) == 0) 98 return (i); 99 return ((pam_facility_t)-1); 100} 101 102/* 103 * Parse the control flag. 104 * 105 * Returns the corresponding pam_control_t value, or -1 if the argument is 106 * not a valid control flag name. 107 */ 108static pam_control_t 109parse_control_flag(const char *name) 110{ 111 int i; 112 113 for (i = 0; i < PAM_NUM_CONTROL_FLAGS; ++i) 114 if (strcmp(pam_control_flag_name[i], name) == 0) 115 return (i); 116 return ((pam_control_t)-1); 117} 118 119/* 120 * Validate a file name. 121 * 122 * Returns a non-zero value if the argument points to a NUL-terminated 123 * string consisting entirely of characters in the POSIX portable filename 124 * character set, including the path separator character. 125 */ 126static int 127valid_module_name(const char *name) 128{ 129 const char *p; 130 131 if (OPENPAM_FEATURE(RESTRICT_MODULE_NAME)) { 132 /* path separator not allowed */ 133 for (p = name; *p != '\0'; ++p) 134 if (!is_pfcs(*p)) 135 return (0); 136 } else { 137 /* path separator allowed */ 138 for (p = name; *p != '\0'; ++p) 139 if (!is_pfcs(*p) && *p != '/') 140 return (0); 141 } 142 return (1); 143} 144 145typedef enum { pam_conf_style, pam_d_style } openpam_style_t; 146 147/* 148 * Extracts given chains from a policy file. 149 * 150 * Returns the number of policy entries which were found for the specified 151 * service and facility, or -1 if a system error occurred or a syntax 152 * error was encountered. 153 */ 154static int 155openpam_parse_chain(pam_handle_t *pamh, 156 const char *service, 157 pam_facility_t facility, 158 FILE *f, 159 const char *filename, 160 openpam_style_t style) 161{ 162 pam_chain_t *this, **next; 163 pam_facility_t fclt; 164 pam_control_t ctlf; 165 char *name, *servicename, *modulename; 166 int count, lineno, ret, serrno; 167 char **wordv, *word; 168 int i, wordc; 169 170 count = 0; 171 this = NULL; 172 name = NULL; 173 lineno = 0; 174 wordc = 0; 175 wordv = NULL; 176 while ((wordv = openpam_readlinev(f, &lineno, &wordc)) != NULL) { 177 /* blank line? */ 178 if (wordc == 0) { 179 FREEV(wordc, wordv); 180 continue; 181 } 182 i = 0; 183 184 /* check service name if necessary */ 185 if (style == pam_conf_style && 186 strcmp(wordv[i++], service) != 0) { 187 FREEV(wordc, wordv); 188 continue; 189 } 190 191 /* check facility name */ 192 if ((word = wordv[i++]) == NULL || 193 (fclt = parse_facility_name(word)) == (pam_facility_t)-1) { 194 openpam_log(PAM_LOG_ERROR, 195 "%s(%d): missing or invalid facility", 196 filename, lineno); 197 goto fail; 198 } 199 if (facility != fclt && facility != PAM_FACILITY_ANY) { 200 FREEV(wordc, wordv); 201 continue; 202 } 203 204 /* check for "include" */ 205 if ((word = wordv[i++]) != NULL && 206 strcmp(word, "include") == 0) { 207 if ((servicename = wordv[i++]) == NULL || 208 !valid_service_name(servicename)) { 209 openpam_log(PAM_LOG_ERROR, 210 "%s(%d): missing or invalid service name", 211 filename, lineno); 212 goto fail; 213 } 214 if (wordv[i] != NULL) { 215 openpam_log(PAM_LOG_ERROR, 216 "%s(%d): garbage at end of line", 217 filename, lineno); 218 goto fail; 219 } 220 ret = openpam_load_chain(pamh, servicename, fclt); 221 FREEV(wordc, wordv); 222 if (ret < 0) 223 goto fail; 224 continue; 225 } 226 227 /* get control flag */ 228 if (word == NULL || /* same word we compared to "include" */ 229 (ctlf = parse_control_flag(word)) == (pam_control_t)-1) { 230 openpam_log(PAM_LOG_ERROR, 231 "%s(%d): missing or invalid control flag", 232 filename, lineno); 233 goto fail; 234 } 235 236 /* get module name */ 237 if ((modulename = wordv[i++]) == NULL || 238 !valid_module_name(modulename)) { 239 openpam_log(PAM_LOG_ERROR, 240 "%s(%d): missing or invalid module name", 241 filename, lineno); 242 goto fail; 243 } 244 245 /* allocate new entry */ 246 if ((this = calloc(1, sizeof *this)) == NULL) 247 goto syserr; 248 this->flag = ctlf; 249 250 /* load module */ 251 if ((this->module = openpam_load_module(modulename)) == NULL) 252 goto fail; 253 254 /* 255 * The remaining items in wordv are the module's 256 * arguments. We could set this->optv = wordv + i, but 257 * then free(this->optv) wouldn't work. Instead, we free 258 * the words we've already consumed, shift the rest up, 259 * and clear the tail end of the array. 260 */ 261 this->optc = wordc - i; 262 for (i = 0; i < wordc - this->optc; ++i) { 263 FREE(wordv[i]); 264 } 265 for (i = 0; i < this->optc; ++i) { 266 wordv[i] = wordv[wordc - this->optc + i]; 267 wordv[wordc - this->optc + i] = NULL; 268 } 269 this->optv = wordv; 270 wordv = NULL; 271 wordc = 0; 272 273 /* hook it up */ 274 for (next = &pamh->chains[fclt]; *next != NULL; 275 next = &(*next)->next) 276 /* nothing */ ; 277 *next = this; 278 this = NULL; 279 ++count; 280 } 281 /* 282 * The loop ended because openpam_readword() returned NULL, which 283 * can happen for four different reasons: an I/O error (ferror(f) 284 * is true), a memory allocation failure (ferror(f) is false, 285 * errno is non-zero) 286 */ 287 if (ferror(f) || errno != 0) 288 goto syserr; 289 if (!feof(f)) 290 goto fail; 291 fclose(f); 292 return (count); 293syserr: 294 serrno = errno; 295 openpam_log(PAM_LOG_ERROR, "%s: %m", filename); 296 errno = serrno; 297 /* fall through */ 298fail: 299 serrno = errno; 300 if (this && this->optc && this->optv) 301 FREEV(this->optc, this->optv); 302 FREE(this); 303 FREEV(wordc, wordv); 304 FREE(wordv); 305 FREE(name); 306 fclose(f); 307 errno = serrno; 308 return (-1); 309} 310 311static const char *openpam_policy_path[] = { 312 "/etc/pam.d/", 313 "/etc/pam.conf", 314 "/usr/local/etc/pam.d/", 315 "/usr/local/etc/pam.conf", 316 NULL 317}; 318 319/* 320 * Read the specified chains from the specified file. 321 * 322 * Returns 0 if the file exists but does not contain any matching lines. 323 * 324 * Returns -1 and sets errno to ENOENT if the file does not exist. 325 * 326 * Returns -1 and sets errno to some other non-zero value if the file 327 * exists but is unsafe or unreadable, or an I/O error occurs. 328 */ 329static int 330openpam_load_file(pam_handle_t *pamh, 331 const char *service, 332 pam_facility_t facility, 333 const char *filename, 334 openpam_style_t style) 335{ 336 FILE *f; 337 int ret, serrno; 338 339 /* attempt to open the file */ 340 if ((f = fopen(filename, "r")) == NULL) { 341 serrno = errno; 342 openpam_log(errno == ENOENT ? PAM_LOG_DEBUG : PAM_LOG_ERROR, 343 "%s: %m", filename); 344 errno = serrno; 345 RETURNN(-1); 346 } else { 347 openpam_log(PAM_LOG_DEBUG, "found %s", filename); 348 } 349 350 /* verify type, ownership and permissions */ 351 if (OPENPAM_FEATURE(VERIFY_POLICY_FILE) && 352 openpam_check_desc_owner_perms(filename, fileno(f)) != 0) { 353 /* already logged the cause */ 354 serrno = errno; 355 fclose(f); 356 errno = serrno; 357 RETURNN(-1); 358 } 359 360 /* parse the file */ 361 ret = openpam_parse_chain(pamh, service, facility, 362 f, filename, style); 363 RETURNN(ret); 364} 365 366/* 367 * Locates the policy file for a given service and reads the given chains 368 * from it. 369 * 370 * Returns the number of policy entries which were found for the specified 371 * service and facility, or -1 if a system error occurred or a syntax 372 * error was encountered. 373 */ 374static int 375openpam_load_chain(pam_handle_t *pamh, 376 const char *service, 377 pam_facility_t facility) 378{ 379 const char *p, **path; 380 char filename[PATH_MAX]; 381 size_t len; 382 openpam_style_t style; 383 int ret; 384 385 ENTERS(facility < 0 ? "any" : pam_facility_name[facility]); 386 387 /* either absolute or relative to cwd */ 388 if (strchr(service, '/') != NULL) { 389 if ((p = strrchr(service, '.')) != NULL && strcmp(p, ".conf") == 0) 390 style = pam_conf_style; 391 else 392 style = pam_d_style; 393 ret = openpam_load_file(pamh, service, facility, 394 service, style); 395 RETURNN(ret); 396 } 397 398 /* search standard locations */ 399 for (path = openpam_policy_path; *path != NULL; ++path) { 400 /* construct filename */ 401 len = strlcpy(filename, *path, sizeof filename); 402 if (filename[len - 1] == '/') { 403 len = strlcat(filename, service, sizeof filename); 404 if (len >= sizeof filename) { 405 errno = ENAMETOOLONG; 406 RETURNN(-1); 407 } 408 style = pam_d_style; 409 } else { 410 style = pam_conf_style; 411 } 412 ret = openpam_load_file(pamh, service, facility, 413 filename, style); 414 /* the file exists, but an error occurred */ 415 if (ret == -1 && errno != ENOENT) 416 RETURNN(ret); 417 /* in pam.d style, an empty file counts as a hit */ 418 if (ret == 0 && style == pam_d_style) 419 RETURNN(ret); 420 } 421 422 /* no hit */ 423 RETURNN(0); 424} 425 426/* 427 * OpenPAM internal 428 * 429 * Configure a service 430 */ 431 432int 433openpam_configure(pam_handle_t *pamh, 434 const char *service) 435{ 436 pam_facility_t fclt; 437 int serrno; 438 439 ENTERS(service); 440 if (!valid_service_name(service)) { 441 openpam_log(PAM_LOG_ERROR, "invalid service name"); 442 RETURNC(PAM_SYSTEM_ERR); 443 } 444 if (openpam_load_chain(pamh, service, PAM_FACILITY_ANY) < 0) 445 goto load_err; 446 for (fclt = 0; fclt < PAM_NUM_FACILITIES; ++fclt) { 447 if (pamh->chains[fclt] != NULL) 448 continue; 449 if (openpam_load_chain(pamh, PAM_OTHER, fclt) < 0) 450 goto load_err; 451 } 452 RETURNC(PAM_SUCCESS); 453load_err: 454 serrno = errno; 455 openpam_clear_chains(pamh->chains); 456 errno = serrno; 457 RETURNC(PAM_SYSTEM_ERR); 458} 459 460/* 461 * NODOC 462 * 463 * Error codes: 464 * PAM_SYSTEM_ERR 465 */ 466