openpam_configure.c revision 267013
152284Sobrien/*- 290075Sobrien * Copyright (c) 2001-2003 Networks Associates Technology, Inc. 352284Sobrien * Copyright (c) 2004-2014 Dag-Erling Sm��rgrav 490075Sobrien * All rights reserved. 552284Sobrien * 690075Sobrien * This software was developed for the FreeBSD Project by ThinkSec AS and 790075Sobrien * Network Associates Laboratories, the Security Research Division of 890075Sobrien * Network Associates, Inc. under DARPA/SPAWAR contract N66001-01-C-8035 990075Sobrien * ("CBOSS"), as part of the DARPA CHATS research program. 1052284Sobrien * 1190075Sobrien * Redistribution and use in source and binary forms, with or without 1290075Sobrien * modification, are permitted provided that the following conditions 1390075Sobrien * are met: 1490075Sobrien * 1. Redistributions of source code must retain the above copyright 1552284Sobrien * notice, this list of conditions and the following disclaimer. 1652284Sobrien * 2. Redistributions in binary form must reproduce the above copyright 1790075Sobrien * notice, this list of conditions and the following disclaimer in the 1890075Sobrien * documentation and/or other materials provided with the distribution. 1990075Sobrien * 3. The name of the author may not be used to endorse or promote 2052284Sobrien * products derived from this software without specific prior written 2190075Sobrien * permission. 2290075Sobrien * 2390075Sobrien * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 2452284Sobrien * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 2552284Sobrien * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 2652284Sobrien * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 2752284Sobrien * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 2890075Sobrien * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 2952284Sobrien * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 3052284Sobrien * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 3190075Sobrien * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 3290075Sobrien * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 3390075Sobrien * SUCH DAMAGE. 3490075Sobrien * 3590075Sobrien * $Id: openpam_configure.c 667 2013-03-17 14:24:00Z des $ 3690075Sobrien */ 3752284Sobrien 3852284Sobrien#ifdef HAVE_CONFIG_H 3952284Sobrien# include "config.h" 4052284Sobrien#endif 4152284Sobrien 4290075Sobrien#include <sys/param.h> 4352284Sobrien 4490075Sobrien#include <errno.h> 4590075Sobrien#include <stdio.h> 4690075Sobrien#include <stdlib.h> 4790075Sobrien#include <string.h> 4852284Sobrien 4990075Sobrien#include <security/pam_appl.h> 5090075Sobrien 5190075Sobrien#include "openpam_impl.h" 5252284Sobrien#include "openpam_ctype.h" 5390075Sobrien#include "openpam_strlcat.h" 5490075Sobrien#include "openpam_strlcpy.h" 5590075Sobrien 5690075Sobrienstatic int openpam_load_chain(pam_handle_t *, const char *, pam_facility_t); 5752284Sobrien 5852284Sobrien/* 5952284Sobrien * Validate a service name. 6052284Sobrien * 6190075Sobrien * Returns a non-zero value if the argument points to a NUL-terminated 6290075Sobrien * string consisting entirely of characters in the POSIX portable filename 6352284Sobrien * character set, excluding the path separator character. 6452284Sobrien */ 6552284Sobrienstatic int 6690075Sobrienvalid_service_name(const char *name) 6790075Sobrien{ 6852284Sobrien const char *p; 6952284Sobrien 7090075Sobrien if (OPENPAM_FEATURE(RESTRICT_SERVICE_NAME)) { 7152284Sobrien /* path separator not allowed */ 7290075Sobrien for (p = name; *p != '\0'; ++p) 7390075Sobrien if (!is_pfcs(*p)) 7490075Sobrien return (0); 7590075Sobrien } else { 7690075Sobrien /* path separator allowed */ 7790075Sobrien for (p = name; *p != '\0'; ++p) 7890075Sobrien if (!is_pfcs(*p) && *p != '/') 7990075Sobrien return (0); 8090075Sobrien } 8190075Sobrien return (1); 8290075Sobrien} 8390075Sobrien 8490075Sobrien/* 8590075Sobrien * Parse the facility name. 8652284Sobrien * 8752284Sobrien * Returns the corresponding pam_facility_t value, or -1 if the argument 8890075Sobrien * is not a valid facility name. 8990075Sobrien */ 9052284Sobrienstatic pam_facility_t 9190075Sobrienparse_facility_name(const char *name) 9252284Sobrien{ 9390075Sobrien int i; 9490075Sobrien 9590075Sobrien for (i = 0; i < PAM_NUM_FACILITIES; ++i) 9690075Sobrien if (strcmp(pam_facility_name[i], name) == 0) 9790075Sobrien return (i); 9890075Sobrien return ((pam_facility_t)-1); 9990075Sobrien} 10090075Sobrien 10190075Sobrien/* 10290075Sobrien * Parse the control flag. 10390075Sobrien * 10490075Sobrien * Returns the corresponding pam_control_t value, or -1 if the argument is 10552284Sobrien * not a valid control flag name. 10690075Sobrien */ 10790075Sobrienstatic pam_control_t 10890075Sobrienparse_control_flag(const char *name) 10990075Sobrien{ 11090075Sobrien int i; 11190075Sobrien 11290075Sobrien for (i = 0; i < PAM_NUM_CONTROL_FLAGS; ++i) 11390075Sobrien if (strcmp(pam_control_flag_name[i], name) == 0) 11490075Sobrien return (i); 11590075Sobrien return ((pam_control_t)-1); 11690075Sobrien} 11790075Sobrien 11852284Sobrien/* 11990075Sobrien * Validate a file name. 12090075Sobrien * 12152284Sobrien * Returns a non-zero value if the argument points to a NUL-terminated 12290075Sobrien * string consisting entirely of characters in the POSIX portable filename 12352284Sobrien * character set, including the path separator character. 12452284Sobrien */ 12552284Sobrienstatic int 12652284Sobrienvalid_module_name(const char *name) 12790075Sobrien{ 12890075Sobrien const char *p; 12952284Sobrien 13052284Sobrien if (OPENPAM_FEATURE(RESTRICT_MODULE_NAME)) { 13190075Sobrien /* path separator not allowed */ 13290075Sobrien for (p = name; *p != '\0'; ++p) 13390075Sobrien if (!is_pfcs(*p)) 13490075Sobrien return (0); 13590075Sobrien } else { 13690075Sobrien /* path separator allowed */ 13790075Sobrien for (p = name; *p != '\0'; ++p) 13890075Sobrien if (!is_pfcs(*p) && *p != '/') 13990075Sobrien return (0); 14090075Sobrien } 14190075Sobrien 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