1 /* 2 * PAM option parsing test suite. 3 * 4 * The canonical version of this file is maintained in the rra-c-util package, 5 * which can be found at <https://www.eyrie.org/~eagle/software/rra-c-util/>. 6 * 7 * Written by Russ Allbery <eagle@eyrie.org> 8 * Copyright 2020 Russ Allbery <eagle@eyrie.org> 9 * Copyright 2010-2014 10 * The Board of Trustees of the Leland Stanford Junior University 11 * 12 * Permission is hereby granted, free of charge, to any person obtaining a 13 * copy of this software and associated documentation files (the "Software"), 14 * to deal in the Software without restriction, including without limitation 15 * the rights to use, copy, modify, merge, publish, distribute, sublicense, 16 * and/or sell copies of the Software, and to permit persons to whom the 17 * Software is furnished to do so, subject to the following conditions: 18 * 19 * The above copyright notice and this permission notice shall be included in 20 * all copies or substantial portions of the Software. 21 * 22 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 25 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 26 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 27 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 28 * DEALINGS IN THE SOFTWARE. 29 * 30 * SPDX-License-Identifier: MIT 31 */ 32 33 #include <config.h> 34 #include <portable/pam.h> 35 #include <portable/system.h> 36 37 #include <syslog.h> 38 39 #include <pam-util/args.h> 40 #include <pam-util/options.h> 41 #include <pam-util/vector.h> 42 #include <tests/fakepam/pam.h> 43 #include <tests/tap/basic.h> 44 #include <tests/tap/string.h> 45 46 /* The configuration struct we will use for testing. */ 47 struct pam_config { 48 struct vector *cells; 49 bool debug; 50 #ifdef HAVE_KRB5 51 krb5_deltat expires; 52 #else 53 long expires; 54 #endif 55 bool ignore_root; 56 long minimum_uid; 57 char *program; 58 }; 59 60 #define K(name) (#name), offsetof(struct pam_config, name) 61 62 /* The rules specifying the configuration options. */ 63 static struct option options[] = { 64 /* clang-format off */ 65 { K(cells), true, LIST (NULL) }, 66 { K(debug), true, BOOL (false) }, 67 { K(expires), true, TIME (10) }, 68 { K(ignore_root), false, BOOL (true) }, 69 { K(minimum_uid), true, NUMBER (0) }, 70 { K(program), true, STRING (NULL) }, 71 /* clang-format on */ 72 }; 73 static const size_t optlen = sizeof(options) / sizeof(options[0]); 74 75 /* 76 * A macro used to parse the various ways of spelling booleans. This reuses 77 * the argv_bool variable, setting it to the first value provided and then 78 * calling putil_args_parse() on it. It then checks whether the provided 79 * config option is set to the expected value. 80 */ 81 #define TEST_BOOL(a, c, v) \ 82 do { \ 83 argv_bool[0] = (a); \ 84 status = putil_args_parse(args, 1, argv_bool, options, optlen); \ 85 ok(status, "Parse of %s", (a)); \ 86 is_int((v), (c), "...and value is correct"); \ 87 ok(pam_output() == NULL, "...and no output"); \ 88 } while (0) 89 90 /* 91 * A macro used to test error reporting from putil_args_parse(). This reuses 92 * the argv_err variable, setting it to the first value provided and then 93 * calling putil_args_parse() on it. It then recovers the error message and 94 * expects it to match the severity and error message given. 95 */ 96 #define TEST_ERROR(a, p, e) \ 97 do { \ 98 argv_err[0] = (a); \ 99 status = putil_args_parse(args, 1, argv_err, options, optlen); \ 100 ok(status, "Parse of %s", (a)); \ 101 seen = pam_output(); \ 102 if (seen == NULL) \ 103 ok_block(2, false, "...no error output"); \ 104 else { \ 105 is_int((p), seen->lines[0].priority, "...priority for %s", (a)); \ 106 is_string((e), seen->lines[0].line, "...error for %s", (a)); \ 107 } \ 108 pam_output_free(seen); \ 109 } while (0) 110 111 112 /* 113 * Allocate and initialize a new struct config. 114 */ 115 static struct pam_config * 116 config_new(void) 117 { 118 return bcalloc(1, sizeof(struct pam_config)); 119 } 120 121 122 /* 123 * Free a struct config and all of its members. 124 */ 125 static void 126 config_free(struct pam_config *config) 127 { 128 if (config == NULL) 129 return; 130 vector_free(config->cells); 131 free(config->program); 132 free(config); 133 } 134 135 136 int 137 main(void) 138 { 139 pam_handle_t *pamh; 140 struct pam_args *args; 141 struct pam_conv conv = {NULL, NULL}; 142 bool status; 143 struct vector *cells; 144 char *program; 145 struct output *seen; 146 const char *argv_bool[2] = {NULL, NULL}; 147 const char *argv_err[2] = {NULL, NULL}; 148 const char *argv_empty[] = {NULL}; 149 #ifdef HAVE_KRB5 150 const char *argv_all[] = {"cells=stanford.edu,ir.stanford.edu", 151 "debug", 152 "expires=1d", 153 "ignore_root", 154 "minimum_uid=1000", 155 "program=/bin/true"}; 156 char *krb5conf; 157 #else 158 const char *argv_all[] = {"cells=stanford.edu,ir.stanford.edu", 159 "debug", 160 "expires=86400", 161 "ignore_root", 162 "minimum_uid=1000", 163 "program=/bin/true"}; 164 #endif 165 166 if (pam_start("test", NULL, &conv, &pamh) != PAM_SUCCESS) 167 sysbail("cannot create pam_handle_t"); 168 args = putil_args_new(pamh, 0); 169 if (args == NULL) 170 bail("cannot create PAM argument struct"); 171 172 plan(161); 173 174 /* First, check just the defaults. */ 175 args->config = config_new(); 176 status = putil_args_defaults(args, options, optlen); 177 ok(status, "Setting the defaults"); 178 ok(args->config->cells == NULL, "...cells default"); 179 is_int(false, args->config->debug, "...debug default"); 180 is_int(10, args->config->expires, "...expires default"); 181 is_int(true, args->config->ignore_root, "...ignore_root default"); 182 is_int(0, args->config->minimum_uid, "...minimum_uid default"); 183 ok(args->config->program == NULL, "...program default"); 184 185 /* Now parse an empty set of PAM arguments. Nothing should change. */ 186 status = putil_args_parse(args, 0, argv_empty, options, optlen); 187 ok(status, "Parse of empty argv"); 188 ok(args->config->cells == NULL, "...cells still default"); 189 is_int(false, args->config->debug, "...debug still default"); 190 is_int(10, args->config->expires, "...expires default"); 191 is_int(true, args->config->ignore_root, "...ignore_root still default"); 192 is_int(0, args->config->minimum_uid, "...minimum_uid still default"); 193 ok(args->config->program == NULL, "...program still default"); 194 195 /* Now, check setting everything. */ 196 status = putil_args_parse(args, 6, argv_all, options, optlen); 197 ok(status, "Parse of full argv"); 198 if (args->config->cells == NULL) 199 ok_block(4, false, "...cells is set"); 200 else { 201 ok(args->config->cells != NULL, "...cells is set"); 202 is_int(2, args->config->cells->count, "...with two cells"); 203 is_string("stanford.edu", args->config->cells->strings[0], 204 "...first is stanford.edu"); 205 is_string("ir.stanford.edu", args->config->cells->strings[1], 206 "...second is ir.stanford.edu"); 207 } 208 is_int(true, args->config->debug, "...debug is set"); 209 is_int(86400, args->config->expires, "...expires is set"); 210 is_int(true, args->config->ignore_root, "...ignore_root is set"); 211 is_int(1000, args->config->minimum_uid, "...minimum_uid is set"); 212 is_string("/bin/true", args->config->program, "...program is set"); 213 config_free(args->config); 214 args->config = NULL; 215 216 /* Test deep copying of defaults. */ 217 cells = vector_new(); 218 if (cells == NULL) 219 sysbail("cannot allocate memory"); 220 vector_add(cells, "foo.com"); 221 vector_add(cells, "bar.com"); 222 options[0].defaults.list = cells; 223 program = strdup("/bin/false"); 224 if (program == NULL) 225 sysbail("cannot allocate memory"); 226 options[5].defaults.string = program; 227 args->config = config_new(); 228 status = putil_args_defaults(args, options, optlen); 229 ok(status, "Setting defaults with new defaults"); 230 if (args->config->cells == NULL) 231 ok_block(4, false, "...cells is set"); 232 else { 233 ok(args->config->cells != NULL, "...cells is set"); 234 is_int(2, args->config->cells->count, "...with two cells"); 235 is_string("foo.com", args->config->cells->strings[0], 236 "...first is foo.com"); 237 is_string("bar.com", args->config->cells->strings[1], 238 "...second is bar.com"); 239 } 240 is_string("/bin/false", args->config->program, "...program is /bin/false"); 241 status = putil_args_parse(args, 6, argv_all, options, optlen); 242 ok(status, "Parse of full argv after defaults"); 243 if (args->config->cells == NULL) 244 ok_block(4, false, "...cells is set"); 245 else { 246 ok(args->config->cells != NULL, "...cells is set"); 247 is_int(2, args->config->cells->count, "...with two cells"); 248 is_string("stanford.edu", args->config->cells->strings[0], 249 "...first is stanford.edu"); 250 is_string("ir.stanford.edu", args->config->cells->strings[1], 251 "...second is ir.stanford.edu"); 252 } 253 is_int(true, args->config->debug, "...debug is set"); 254 is_int(86400, args->config->expires, "...expires is set"); 255 is_int(true, args->config->ignore_root, "...ignore_root is set"); 256 is_int(1000, args->config->minimum_uid, "...minimum_uid is set"); 257 is_string("/bin/true", args->config->program, "...program is set"); 258 is_string("foo.com", cells->strings[0], "...first cell after parse"); 259 is_string("bar.com", cells->strings[1], "...second cell after parse"); 260 is_string("/bin/false", program, "...string after parse"); 261 config_free(args->config); 262 args->config = NULL; 263 is_string("foo.com", cells->strings[0], "...first cell after free"); 264 is_string("bar.com", cells->strings[1], "...second cell after free"); 265 is_string("/bin/false", program, "...string after free"); 266 options[0].defaults.list = NULL; 267 options[5].defaults.string = NULL; 268 vector_free(cells); 269 free(program); 270 271 /* Test specifying the default for a vector parameter as a string. */ 272 options[0].type = TYPE_STRLIST; 273 options[0].defaults.string = "foo.com,bar.com"; 274 args->config = config_new(); 275 status = putil_args_defaults(args, options, optlen); 276 ok(status, "Setting defaults with string default for vector"); 277 if (args->config->cells == NULL) 278 ok_block(4, false, "...cells is set"); 279 else { 280 ok(args->config->cells != NULL, "...cells is set"); 281 is_int(2, args->config->cells->count, "...with two cells"); 282 is_string("foo.com", args->config->cells->strings[0], 283 "...first is foo.com"); 284 is_string("bar.com", args->config->cells->strings[1], 285 "...second is bar.com"); 286 } 287 config_free(args->config); 288 args->config = NULL; 289 options[0].type = TYPE_LIST; 290 options[0].defaults.string = NULL; 291 292 /* Should be no errors so far. */ 293 ok(pam_output() == NULL, "No errors so far"); 294 295 /* Test various ways of spelling booleans. */ 296 args->config = config_new(); 297 TEST_BOOL("debug", args->config->debug, true); 298 TEST_BOOL("debug=false", args->config->debug, false); 299 TEST_BOOL("debug=true", args->config->debug, true); 300 TEST_BOOL("debug=no", args->config->debug, false); 301 TEST_BOOL("debug=yes", args->config->debug, true); 302 TEST_BOOL("debug=off", args->config->debug, false); 303 TEST_BOOL("debug=on", args->config->debug, true); 304 TEST_BOOL("debug=0", args->config->debug, false); 305 TEST_BOOL("debug=1", args->config->debug, true); 306 TEST_BOOL("debug=False", args->config->debug, false); 307 TEST_BOOL("debug=trUe", args->config->debug, true); 308 TEST_BOOL("debug=No", args->config->debug, false); 309 TEST_BOOL("debug=Yes", args->config->debug, true); 310 TEST_BOOL("debug=OFF", args->config->debug, false); 311 TEST_BOOL("debug=ON", args->config->debug, true); 312 config_free(args->config); 313 args->config = NULL; 314 315 /* Test for various parsing errors. */ 316 args->config = config_new(); 317 TEST_ERROR("debug=", LOG_ERR, "invalid boolean in setting: debug="); 318 TEST_ERROR("debug=truth", LOG_ERR, 319 "invalid boolean in setting: debug=truth"); 320 TEST_ERROR("minimum_uid", LOG_ERR, "value missing for option minimum_uid"); 321 TEST_ERROR("minimum_uid=", LOG_ERR, 322 "value missing for option minimum_uid="); 323 TEST_ERROR("minimum_uid=foo", LOG_ERR, 324 "invalid number in setting: minimum_uid=foo"); 325 TEST_ERROR("minimum_uid=1000foo", LOG_ERR, 326 "invalid number in setting: minimum_uid=1000foo"); 327 TEST_ERROR("program", LOG_ERR, "value missing for option program"); 328 TEST_ERROR("cells", LOG_ERR, "value missing for option cells"); 329 config_free(args->config); 330 args->config = NULL; 331 332 #ifdef HAVE_KRB5 333 334 /* Test for Kerberos krb5.conf option parsing. */ 335 krb5conf = test_file_path("data/krb5-pam.conf"); 336 if (krb5conf == NULL) 337 bail("cannot find data/krb5-pam.conf"); 338 if (setenv("KRB5_CONFIG", krb5conf, 1) < 0) 339 sysbail("cannot set KRB5_CONFIG"); 340 krb5_free_context(args->ctx); 341 status = krb5_init_context(&args->ctx); 342 if (status != 0) 343 bail("cannot parse test krb5.conf file"); 344 args->config = config_new(); 345 status = putil_args_defaults(args, options, optlen); 346 ok(status, "Setting the defaults"); 347 status = putil_args_krb5(args, "testing", options, optlen); 348 ok(status, "Options from krb5.conf"); 349 ok(args->config->cells == NULL, "...cells default"); 350 is_int(true, args->config->debug, "...debug set from krb5.conf"); 351 is_int(1800, args->config->expires, "...expires set from krb5.conf"); 352 is_int(true, args->config->ignore_root, "...ignore_root default"); 353 is_int(1000, args->config->minimum_uid, 354 "...minimum_uid set from krb5.conf"); 355 ok(args->config->program == NULL, "...program default"); 356 status = putil_args_krb5(args, "other-test", options, optlen); 357 ok(status, "Options from krb5.conf (other-test)"); 358 is_int(-1000, args->config->minimum_uid, 359 "...minimum_uid set from krb5.conf other-test"); 360 361 /* Test with a realm set, which should expose more settings. */ 362 krb5_free_context(args->ctx); 363 status = krb5_init_context(&args->ctx); 364 if (status != 0) 365 bail("cannot parse test krb5.conf file"); 366 args->realm = strdup("FOO.COM"); 367 if (args->realm == NULL) 368 sysbail("cannot allocate memory"); 369 status = putil_args_krb5(args, "testing", options, optlen); 370 ok(status, "Options from krb5.conf with FOO.COM"); 371 is_int(2, args->config->cells->count, "...cells count from krb5.conf"); 372 is_string("foo.com", args->config->cells->strings[0], 373 "...first cell from krb5.conf"); 374 is_string("bar.com", args->config->cells->strings[1], 375 "...second cell from krb5.conf"); 376 is_int(true, args->config->debug, "...debug set from krb5.conf"); 377 is_int(1800, args->config->expires, "...expires set from krb5.conf"); 378 is_int(true, args->config->ignore_root, "...ignore_root default"); 379 is_int(1000, args->config->minimum_uid, 380 "...minimum_uid set from krb5.conf"); 381 is_string("/bin/false", args->config->program, 382 "...program from krb5.conf"); 383 384 /* Test with a different realm. */ 385 free(args->realm); 386 args->realm = strdup("BAR.COM"); 387 if (args->realm == NULL) 388 sysbail("cannot allocate memory"); 389 status = putil_args_krb5(args, "testing", options, optlen); 390 ok(status, "Options from krb5.conf with BAR.COM"); 391 is_int(2, args->config->cells->count, "...cells count from krb5.conf"); 392 is_string("bar.com", args->config->cells->strings[0], 393 "...first cell from krb5.conf"); 394 is_string("foo.com", args->config->cells->strings[1], 395 "...second cell from krb5.conf"); 396 is_int(true, args->config->debug, "...debug set from krb5.conf"); 397 is_int(1800, args->config->expires, "...expires set from krb5.conf"); 398 is_int(true, args->config->ignore_root, "...ignore_root default"); 399 is_int(1000, args->config->minimum_uid, 400 "...minimum_uid set from krb5.conf"); 401 is_string("echo /bin/true", args->config->program, 402 "...program from krb5.conf"); 403 config_free(args->config); 404 args->config = config_new(); 405 status = putil_args_krb5(args, "other-test", options, optlen); 406 ok(status, "Options from krb5.conf (other-test with realm)"); 407 ok(args->config->cells == NULL, "...cells is NULL"); 408 is_string("echo /bin/true", args->config->program, 409 "...program from krb5.conf"); 410 config_free(args->config); 411 args->config = NULL; 412 413 /* Test for time parsing errors. */ 414 args->config = config_new(); 415 TEST_ERROR("expires=ft87", LOG_ERR, 416 "bad time value in setting: expires=ft87"); 417 config_free(args->config); 418 419 /* Test error reporting from the krb5.conf parser. */ 420 args->config = config_new(); 421 status = putil_args_krb5(args, "bad-number", options, optlen); 422 ok(status, "Options from krb5.conf (bad-number)"); 423 seen = pam_output(); 424 is_string("invalid number in krb5.conf setting for minimum_uid: 1000foo", 425 seen->lines[0].line, "...and correct error reported"); 426 is_int(LOG_ERR, seen->lines[0].priority, "...with correct priority"); 427 pam_output_free(seen); 428 config_free(args->config); 429 args->config = NULL; 430 431 /* Test error reporting on times from the krb5.conf parser. */ 432 args->config = config_new(); 433 status = putil_args_krb5(args, "bad-time", options, optlen); 434 ok(status, "Options from krb5.conf (bad-time)"); 435 seen = pam_output(); 436 if (seen == NULL) 437 ok_block(2, false, "...no error output"); 438 else { 439 is_string("invalid time in krb5.conf setting for expires: ft87", 440 seen->lines[0].line, "...and correct error reported"); 441 is_int(LOG_ERR, seen->lines[0].priority, "...with correct priority"); 442 } 443 pam_output_free(seen); 444 config_free(args->config); 445 args->config = NULL; 446 447 test_file_path_free(krb5conf); 448 449 #else /* !HAVE_KRB5 */ 450 451 skip_block(37, "Kerberos support not configured"); 452 453 #endif 454 455 putil_args_free(args); 456 pam_end(pamh, 0); 457 return 0; 458 } 459