/* * Internal helper function for remctld_start and remctld_start_fakeroot. * * Takes the Kerberos test configuration (the keytab principal is used as the * server principal), the configuration file to use (found via * test_file_path), and then any additional arguments to pass to remctld, * ending with a NULL. Returns the PID of the running remctld process. If * anything fails, calls bail. * * The path to remctld is obtained from the PATH_REMCTLD #define. If this is * not set, remctld_start_internal calls skip_all. */ static struct process * remctld_start_internal(struct kerberos_config *krbconf, const char *config, va_list args, bool fakeroot) { va_list args_copy; char *tmpdir, *pidfile, *confpath; size_t i, length; const char *arg, **argv; struct process *process; const char *path_remctld = PATH_REMCTLD; /* Check prerequisites. */ if (path_remctld[0] == '\0') skip_all("remctld not found"); /* Determine the location of the PID file and remove any old ones. */ tmpdir = test_tmpdir(); basprintf(&pidfile, "%s/remctld.pid", tmpdir); if (access(pidfile, F_OK) == 0) bail("remctld may already be running: %s exists", pidfile); /* Build the argv used to run remctld. */ confpath = test_file_path(config); if (confpath == NULL) bail("cannot find remctld config %s", config); length = 11; va_copy(args_copy, args); while ((arg = va_arg(args_copy, const char *)) != NULL) length++; va_end(args_copy); argv = bmalloc(length * sizeof(const char *)); i = 0; argv[i++] = path_remctld; argv[i++] = "-mdSF"; argv[i++] = "-p"; argv[i++] = "14373"; argv[i++] = "-s"; argv[i++] = krbconf->principal; argv[i++] = "-P"; argv[i++] = pidfile; argv[i++] = "-f"; argv[i++] = confpath; while ((arg = va_arg(args, const char *)) != NULL) argv[i++] = arg; argv[i] = NULL; /* Start remctld using process_start or process_start_fakeroot. */ if (fakeroot) process = process_start_fakeroot(argv, pidfile); else process = process_start(argv, pidfile); /* Clean up and return. */ test_file_path_free(confpath); free(pidfile); test_tmpdir_free(tmpdir); free(argv); return process; }
static void kerberos_kinit(void) { static const char * const format[] = { "kinit --no-afslog -k -t %s %s >/dev/null 2>&1 </dev/null", "kinit -k -t %s %s >/dev/null 2>&1 </dev/null", "kinit -t %s %s >/dev/null 2>&1 </dev/null", "kinit -k -K %s %s >/dev/null 2>&1 </dev/null" }; FILE *file; char *path; char principal[BUFSIZ], *command; size_t i; int status; /* Read the principal corresponding to the keytab. */ path = test_file_path("config/principal"); if (path == NULL) { test_file_path_free(config->keytab); config->keytab = NULL; return; } file = fopen(path, "r"); if (file == NULL) { test_file_path_free(path); return; } test_file_path_free(path); if (fgets(principal, sizeof(principal), file) == NULL) bail("cannot read %s", path); fclose(file); if (principal[strlen(principal) - 1] != '\n') bail("no newline in %s", path); principal[strlen(principal) - 1] = '\0'; config->principal = bstrdup(principal); /* Now do the Kerberos initialization. */ for (i = 0; i < ARRAY_SIZE(format); i++) { basprintf(&command, format[i], config->keytab, principal); status = system(command); free(command); if (status != -1 && WEXITSTATUS(status) == 0) break; } if (status == -1 || WEXITSTATUS(status) != 0) bail("cannot get Kerberos tickets"); }
/* * Generate a krb5.conf file for testing and set KRB5_CONFIG to point to it. * The [appdefaults] section will be stripped out and the default realm will * be set to the realm specified, if not NULL. This will use config/krb5.conf * in preference, so users can configure the tests by creating that file if * the system file isn't suitable. * * Depends on data/generate-krb5-conf being present in the test suite. */ void kerberos_generate_conf(const char *realm) { char *path; const char *argv[3]; if (tmpdir_conf != NULL) kerberos_cleanup_conf(); path = test_file_path("data/generate-krb5-conf"); if (path == NULL) bail("cannot find generate-krb5-conf"); argv[0] = path; argv[1] = realm; argv[2] = NULL; run_setup(argv); test_file_path_free(path); tmpdir_conf = test_tmpdir(); basprintf(&krb5_config, "KRB5_CONFIG=%s/krb5.conf", tmpdir_conf); putenv(krb5_config); if (atexit(kerberos_cleanup_conf) != 0) sysdiag("cannot register cleanup function"); }
/* * Obtain Kerberos tickets for the principal specified in config/principal * using the keytab specified in config/keytab, both of which are presumed to * be in tests in either the build or the source tree. Also sets KRB5_KTNAME * and KRB5CCNAME. * * Returns the contents of config/principal in newly allocated memory or NULL * if Kerberos tests are apparently not configured. If Kerberos tests are * configured but something else fails, calls bail. */ struct kerberos_config * kerberos_setup(enum kerberos_needs needs) { char *path; char buffer[BUFSIZ]; FILE *file = NULL; /* If we were called before, clean up after the previous run. */ if (config != NULL) kerberos_cleanup(); config = bcalloc(1, sizeof(struct kerberos_config)); /* * If we have a config/keytab file, set the KRB5CCNAME and KRB5_KTNAME * environment variables and obtain initial tickets. */ config->keytab = test_file_path("config/keytab"); if (config->keytab == NULL) { if (needs == TAP_KRB_NEEDS_KEYTAB || needs == TAP_KRB_NEEDS_BOTH) skip_all("Kerberos tests not configured"); } else { tmpdir_ticket = test_tmpdir(); basprintf(&config->cache, "%s/krb5cc_test", tmpdir_ticket); basprintf(&krb5ccname, "KRB5CCNAME=%s/krb5cc_test", tmpdir_ticket); basprintf(&krb5_ktname, "KRB5_KTNAME=%s", config->keytab); putenv(krb5ccname); putenv(krb5_ktname); kerberos_kinit(); } /* * If we have a config/password file, read it and fill out the relevant * members of our config struct. */ path = test_file_path("config/password"); if (path != NULL) file = fopen(path, "r"); if (file == NULL) { if (needs == TAP_KRB_NEEDS_PASSWORD || needs == TAP_KRB_NEEDS_BOTH) skip_all("Kerberos tests not configured"); } else { if (fgets(buffer, sizeof(buffer), file) == NULL) bail("cannot read %s", path); if (buffer[strlen(buffer) - 1] != '\n') bail("no newline in %s", path); buffer[strlen(buffer) - 1] = '\0'; config->userprinc = bstrdup(buffer); if (fgets(buffer, sizeof(buffer), file) == NULL) bail("cannot read password from %s", path); fclose(file); if (buffer[strlen(buffer) - 1] != '\n') bail("password too long in %s", path); buffer[strlen(buffer) - 1] = '\0'; config->password = bstrdup(buffer); /* * Strip the realm from the principal and set realm and username. * This is not strictly correct; it doesn't cope with escaped @-signs * or enterprise names. */ config->username = bstrdup(config->userprinc); config->realm = strchr(config->username, '@'); if (config->realm == NULL) bail("test principal has no realm"); *config->realm = '\0'; config->realm++; } test_file_path_free(path); /* * If we have PKINIT configuration, read it and fill out the relevant * members of our config struct. */ path = test_file_path("config/pkinit-principal"); if (path != NULL) file = fopen(path, "r"); if (file != NULL) { if (fgets(buffer, sizeof(buffer), file) == NULL) bail("cannot read %s", path); if (buffer[strlen(buffer) - 1] != '\n') bail("no newline in %s", path); buffer[strlen(buffer) - 1] = '\0'; fclose(file); test_file_path_free(path); path = test_file_path("config/pkinit-cert"); if (path != NULL) { config->pkinit_principal = bstrdup(buffer); config->pkinit_cert = bstrdup(path); } } test_file_path_free(path); if (config->pkinit_cert == NULL && (needs & TAP_KRB_NEEDS_PKINIT) != 0) skip_all("PKINIT tests not configured"); /* * Register the cleanup function so that the caller doesn't have to do * explicit cleanup. */ test_cleanup_register(kerberos_cleanup_handler); /* Return the configuration. */ return config; }
int main(void) { pam_handle_t *pamh; struct pam_args *args; struct pam_conv conv = { NULL, NULL }; bool status; struct vector *cells; char *program; struct output *seen; const char *argv_bool[2] = { NULL, NULL }; const char *argv_err[2] = { NULL, NULL }; const char *argv_empty[] = { NULL }; #ifdef HAVE_KRB5 const char *argv_all[] = { "cells=stanford.edu,ir.stanford.edu", "debug", "expires=1d", "ignore_root", "minimum_uid=1000", "program=/bin/true" }; char *krb5conf; #else const char *argv_all[] = { "cells=stanford.edu,ir.stanford.edu", "debug", "expires=86400", "ignore_root", "minimum_uid=1000", "program=/bin/true" }; #endif if (pam_start("test", NULL, &conv, &pamh) != PAM_SUCCESS) sysbail("cannot create pam_handle_t"); args = putil_args_new(pamh, 0); if (args == NULL) bail("cannot create PAM argument struct"); plan(161); /* First, check just the defaults. */ args->config = config_new(); status = putil_args_defaults(args, options, optlen); ok(status, "Setting the defaults"); ok(args->config->cells == NULL, "...cells default"); is_int(false, args->config->debug, "...debug default"); is_int(10, args->config->expires, "...expires default"); is_int(true, args->config->ignore_root, "...ignore_root default"); is_int(0, args->config->minimum_uid, "...minimum_uid default"); ok(args->config->program == NULL, "...program default"); /* Now parse an empty set of PAM arguments. Nothing should change. */ status = putil_args_parse(args, 0, argv_empty, options, optlen); ok(status, "Parse of empty argv"); ok(args->config->cells == NULL, "...cells still default"); is_int(false, args->config->debug, "...debug still default"); is_int(10, args->config->expires, "...expires default"); is_int(true, args->config->ignore_root, "...ignore_root still default"); is_int(0, args->config->minimum_uid, "...minimum_uid still default"); ok(args->config->program == NULL, "...program still default"); /* Now, check setting everything. */ status = putil_args_parse(args, 6, argv_all, options, optlen); ok(status, "Parse of full argv"); if (args->config->cells == NULL) ok_block(4, false, "...cells is set"); else { ok(args->config->cells != NULL, "...cells is set"); is_int(2, args->config->cells->count, "...with two cells"); is_string("stanford.edu", args->config->cells->strings[0], "...first is stanford.edu"); is_string("ir.stanford.edu", args->config->cells->strings[1], "...second is ir.stanford.edu"); } is_int(true, args->config->debug, "...debug is set"); is_int(86400, args->config->expires, "...expires is set"); is_int(true, args->config->ignore_root, "...ignore_root is set"); is_int(1000, args->config->minimum_uid, "...minimum_uid is set"); is_string("/bin/true", args->config->program, "...program is set"); config_free(args->config); args->config = NULL; /* Test deep copying of defaults. */ cells = vector_new(); if (cells == NULL) sysbail("cannot allocate memory"); vector_add(cells, "foo.com"); vector_add(cells, "bar.com"); options[0].defaults.list = cells; program = strdup("/bin/false"); if (program == NULL) sysbail("cannot allocate memory"); options[5].defaults.string = program; args->config = config_new(); status = putil_args_defaults(args, options, optlen); ok(status, "Setting defaults with new defaults"); if (args->config->cells == NULL) ok_block(4, false, "...cells is set"); else { ok(args->config->cells != NULL, "...cells is set"); is_int(2, args->config->cells->count, "...with two cells"); is_string("foo.com", args->config->cells->strings[0], "...first is foo.com"); is_string("bar.com", args->config->cells->strings[1], "...second is bar.com"); } is_string("/bin/false", args->config->program, "...program is /bin/false"); status = putil_args_parse(args, 6, argv_all, options, optlen); ok(status, "Parse of full argv after defaults"); if (args->config->cells == NULL) ok_block(4, false, "...cells is set"); else { ok(args->config->cells != NULL, "...cells is set"); is_int(2, args->config->cells->count, "...with two cells"); is_string("stanford.edu", args->config->cells->strings[0], "...first is stanford.edu"); is_string("ir.stanford.edu", args->config->cells->strings[1], "...second is ir.stanford.edu"); } is_int(true, args->config->debug, "...debug is set"); is_int(86400, args->config->expires, "...expires is set"); is_int(true, args->config->ignore_root, "...ignore_root is set"); is_int(1000, args->config->minimum_uid, "...minimum_uid is set"); is_string("/bin/true", args->config->program, "...program is set"); is_string("foo.com", cells->strings[0], "...first cell after parse"); is_string("bar.com", cells->strings[1], "...second cell after parse"); is_string("/bin/false", program, "...string after parse"); config_free(args->config); args->config = NULL; is_string("foo.com", cells->strings[0], "...first cell after free"); is_string("bar.com", cells->strings[1], "...second cell after free"); is_string("/bin/false", program, "...string after free"); options[0].defaults.list = NULL; options[5].defaults.string = NULL; vector_free(cells); free(program); /* Test specifying the default for a vector parameter as a string. */ options[0].type = TYPE_STRLIST; options[0].defaults.string = "foo.com,bar.com"; args->config = config_new(); status = putil_args_defaults(args, options, optlen); ok(status, "Setting defaults with string default for vector"); if (args->config->cells == NULL) ok_block(4, false, "...cells is set"); else { ok(args->config->cells != NULL, "...cells is set"); is_int(2, args->config->cells->count, "...with two cells"); is_string("foo.com", args->config->cells->strings[0], "...first is foo.com"); is_string("bar.com", args->config->cells->strings[1], "...second is bar.com"); } config_free(args->config); args->config = NULL; options[0].type = TYPE_LIST; options[0].defaults.string = NULL; /* Should be no errors so far. */ ok(pam_output() == NULL, "No errors so far"); /* Test various ways of spelling booleans. */ args->config = config_new(); TEST_BOOL("debug", args->config->debug, true); TEST_BOOL("debug=false", args->config->debug, false); TEST_BOOL("debug=true", args->config->debug, true); TEST_BOOL("debug=no", args->config->debug, false); TEST_BOOL("debug=yes", args->config->debug, true); TEST_BOOL("debug=off", args->config->debug, false); TEST_BOOL("debug=on", args->config->debug, true); TEST_BOOL("debug=0", args->config->debug, false); TEST_BOOL("debug=1", args->config->debug, true); TEST_BOOL("debug=False", args->config->debug, false); TEST_BOOL("debug=trUe", args->config->debug, true); TEST_BOOL("debug=No", args->config->debug, false); TEST_BOOL("debug=Yes", args->config->debug, true); TEST_BOOL("debug=OFF", args->config->debug, false); TEST_BOOL("debug=ON", args->config->debug, true); config_free(args->config); args->config = NULL; /* Test for various parsing errors. */ args->config = config_new(); TEST_ERROR("debug=", LOG_ERR, "invalid boolean in setting: debug="); TEST_ERROR("debug=truth", LOG_ERR, "invalid boolean in setting: debug=truth"); TEST_ERROR("minimum_uid", LOG_ERR, "value missing for option minimum_uid"); TEST_ERROR("minimum_uid=", LOG_ERR, "value missing for option minimum_uid="); TEST_ERROR("minimum_uid=foo", LOG_ERR, "invalid number in setting: minimum_uid=foo"); TEST_ERROR("minimum_uid=1000foo", LOG_ERR, "invalid number in setting: minimum_uid=1000foo"); TEST_ERROR("program", LOG_ERR, "value missing for option program"); TEST_ERROR("cells", LOG_ERR, "value missing for option cells"); config_free(args->config); args->config = NULL; #ifdef HAVE_KRB5 /* Test for Kerberos krb5.conf option parsing. */ krb5conf = test_file_path("data/krb5-pam.conf"); if (krb5conf == NULL) bail("cannot find data/krb5-pam.conf"); if (setenv("KRB5_CONFIG", krb5conf, 1) < 0) sysbail("cannot set KRB5_CONFIG"); krb5_free_context(args->ctx); status = krb5_init_context(&args->ctx); if (status != 0) bail("cannot parse test krb5.conf file"); args->config = config_new(); status = putil_args_defaults(args, options, optlen); ok(status, "Setting the defaults"); status = putil_args_krb5(args, "testing", options, optlen); ok(status, "Options from krb5.conf"); ok(args->config->cells == NULL, "...cells default"); is_int(true, args->config->debug, "...debug set from krb5.conf"); is_int(1800, args->config->expires, "...expires set from krb5.conf"); is_int(true, args->config->ignore_root, "...ignore_root default"); is_int(1000, args->config->minimum_uid, "...minimum_uid set from krb5.conf"); ok(args->config->program == NULL, "...program default"); status = putil_args_krb5(args, "other-test", options, optlen); ok(status, "Options from krb5.conf (other-test)"); is_int(-1000, args->config->minimum_uid, "...minimum_uid set from krb5.conf other-test"); /* Test with a realm set, which should expose more settings. */ krb5_free_context(args->ctx); status = krb5_init_context(&args->ctx); if (status != 0) bail("cannot parse test krb5.conf file"); args->realm = strdup("FOO.COM"); if (args->realm == NULL) sysbail("cannot allocate memory"); status = putil_args_krb5(args, "testing", options, optlen); ok(status, "Options from krb5.conf with FOO.COM"); is_int(2, args->config->cells->count, "...cells count from krb5.conf"); is_string("foo.com", args->config->cells->strings[0], "...first cell from krb5.conf"); is_string("bar.com", args->config->cells->strings[1], "...second cell from krb5.conf"); is_int(true, args->config->debug, "...debug set from krb5.conf"); is_int(1800, args->config->expires, "...expires set from krb5.conf"); is_int(true, args->config->ignore_root, "...ignore_root default"); is_int(1000, args->config->minimum_uid, "...minimum_uid set from krb5.conf"); is_string("/bin/false", args->config->program, "...program from krb5.conf"); /* Test with a different realm. */ free(args->realm); args->realm = strdup("BAR.COM"); if (args->realm == NULL) sysbail("cannot allocate memory"); status = putil_args_krb5(args, "testing", options, optlen); ok(status, "Options from krb5.conf with BAR.COM"); is_int(2, args->config->cells->count, "...cells count from krb5.conf"); is_string("bar.com", args->config->cells->strings[0], "...first cell from krb5.conf"); is_string("foo.com", args->config->cells->strings[1], "...second cell from krb5.conf"); is_int(true, args->config->debug, "...debug set from krb5.conf"); is_int(1800, args->config->expires, "...expires set from krb5.conf"); is_int(true, args->config->ignore_root, "...ignore_root default"); is_int(1000, args->config->minimum_uid, "...minimum_uid set from krb5.conf"); is_string("echo /bin/true", args->config->program, "...program from krb5.conf"); config_free(args->config); args->config = config_new(); status = putil_args_krb5(args, "other-test", options, optlen); ok(status, "Options from krb5.conf (other-test with realm)"); ok(args->config->cells == NULL, "...cells is NULL"); is_string("echo /bin/true", args->config->program, "...program from krb5.conf"); config_free(args->config); args->config = NULL; /* Test for time parsing errors. */ args->config = config_new(); TEST_ERROR("expires=ft87", LOG_ERR, "bad time value in setting: expires=ft87"); config_free(args->config); /* Test error reporting from the krb5.conf parser. */ args->config = config_new(); status = putil_args_krb5(args, "bad-number", options, optlen); ok(status, "Options from krb5.conf (bad-number)"); seen = pam_output(); is_string("invalid number in krb5.conf setting for minimum_uid: 1000foo", seen->lines[0].line, "...and correct error reported"); is_int(LOG_ERR, seen->lines[0].priority, "...with correct priority"); pam_output_free(seen); config_free(args->config); args->config = NULL; /* Test error reporting on times from the krb5.conf parser. */ args->config = config_new(); status = putil_args_krb5(args, "bad-time", options, optlen); ok(status, "Options from krb5.conf (bad-time)"); seen = pam_output(); if (seen == NULL) ok_block(2, false, "...no error output"); else { is_string("invalid time in krb5.conf setting for expires: ft87", seen->lines[0].line, "...and correct error reported"); is_int(LOG_ERR, seen->lines[0].priority, "...with correct priority"); } pam_output_free(seen); config_free(args->config); args->config = NULL; test_file_path_free(krb5conf); #else /* !HAVE_KRB5 */ skip_block(37, "Kerberos support not configured"); #endif putil_args_free(args); pam_end(pamh, 0); return 0; }
bool kerberos_expire_password(const char *principal, time_t expires) { char *path, *user; const char *realm; krb5_context ctx; krb5_principal admin = NULL; krb5_principal princ = NULL; krb5_error_code code; kadm5_config_params params; kadm5_principal_ent_rec ent; void *handle; bool okay = false; /* Set up for making our call. */ path = test_file_path("config/admin-keytab"); if (path == NULL) return false; code = krb5_init_context(&ctx); if (code != 0) bail_krb5(ctx, code, "error initializing Kerberos"); admin = kerberos_keytab_principal(ctx, path); realm = krb5_principal_get_realm(ctx, admin); code = krb5_set_default_realm(ctx, realm); if (code != 0) bail_krb5(ctx, code, "cannot set default realm"); code = krb5_unparse_name(ctx, admin, &user); if (code != 0) bail_krb5(ctx, code, "cannot unparse admin principal"); code = krb5_parse_name(ctx, principal, &princ); if (code != 0) bail_krb5(ctx, code, "cannot parse principal %s", principal); /* * If the actual kadmin calls fail, we may be built with MIT Kerberos * against a Heimdal server or vice versa. Return false to skip the * tests. */ memset(¶ms, 0, sizeof(params)); params.realm = (char *) realm; params.mask = KADM5_CONFIG_REALM; code = kadm5_init_with_skey_ctx(ctx, user, path, KADM5_ADMIN_SERVICE, ¶ms, KADM5_STRUCT_VERSION, KADM5_API_VERSION, &handle); if (code != 0) { diag_krb5(ctx, code, "error initializing kadmin"); goto done; } memset(&ent, 0, sizeof(ent)); ent.principal = princ; ent.pw_expiration = expires; code = kadm5_modify_principal(handle, &ent, KADM5_PW_EXPIRATION); if (code == 0) okay = true; else diag_krb5(ctx, code, "error setting password expiration"); done: kadm5_destroy(handle); krb5_free_unparsed_name(ctx, user); krb5_free_principal(ctx, admin); krb5_free_principal(ctx, princ); krb5_free_context(ctx); test_file_path_free(path); return okay; }