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