Blame SOURCES/Do-expiration-warnings-for-all-init_creds-APIs.patch

3eb3db
From ef3a3cf3ad8b389864f6f6b0535a40ee7e3bb945 Mon Sep 17 00:00:00 2001
3eb3db
From: Sumit Bose <sbose@redhat.com>
3eb3db
Date: Fri, 28 Feb 2020 10:11:49 +0100
3eb3db
Subject: [PATCH] Do expiration warnings for all init_creds APIs
3eb3db
3eb3db
Move the password expiration warning code from gic_pwd.c to
3eb3db
get_in_tkt.c.  Call it from init_creds_step_reply() on successful
3eb3db
completion.
3eb3db
3eb3db
[ghudson@mit.edu: added test case; simplified doc comment; moved call
3eb3db
site to init_creds_step_reply(); rewrote commit message]
3eb3db
3eb3db
ticket: 8893 (new)
3eb3db
(cherry picked from commit e1efb890f7ac31b32c68ab816ef118dbfb5a8c7e)
3eb3db
(cherry picked from commit 9d452dc135ba0fad9470f096938a5dbfbacdbbe1)
3eb3db
[rharwood@redhat.com: fuzz around expected_msg]
3eb3db
---
3eb3db
 src/include/krb5/krb5.hin         |   9 ++-
3eb3db
 src/lib/krb5/krb/get_in_tkt.c     | 112 ++++++++++++++++++++++++++++++
3eb3db
 src/lib/krb5/krb/gic_pwd.c        | 110 -----------------------------
3eb3db
 src/lib/krb5/krb/t_expire_warn.c  |  47 +++++++++----
3eb3db
 src/lib/krb5/krb/t_expire_warn.py |  29 ++++----
3eb3db
 5 files changed, 166 insertions(+), 141 deletions(-)
3eb3db
3eb3db
diff --git a/src/include/krb5/krb5.hin b/src/include/krb5/krb5.hin
3eb3db
index 28557659e..606ceed3a 100644
3eb3db
--- a/src/include/krb5/krb5.hin
3eb3db
+++ b/src/include/krb5/krb5.hin
3eb3db
@@ -7209,11 +7209,10 @@ typedef void
3eb3db
  *
3eb3db
  * Set a callback to receive password and account expiration times.
3eb3db
  *
3eb3db
- * This option only applies to krb5_get_init_creds_password().  @a cb will be
3eb3db
- * invoked if and only if credentials are successfully acquired.  The callback
3eb3db
- * will receive the @a context from the krb5_get_init_creds_password() call and
3eb3db
- * the @a data argument supplied with this API.  The remaining arguments should
3eb3db
- * be interpreted as follows:
3eb3db
+ * @a cb will be invoked if and only if credentials are successfully acquired.
3eb3db
+ * The callback will receive the @a context from the calling function and the
3eb3db
+ * @a data argument supplied with this API.  The remaining arguments should be
3eb3db
+ * interpreted as follows:
3eb3db
  *
3eb3db
  * If @a is_last_req is true, then the KDC reply contained last-req entries
3eb3db
  * which unambiguously indicated the password expiration, account expiration,
3eb3db
diff --git a/src/lib/krb5/krb/get_in_tkt.c b/src/lib/krb5/krb/get_in_tkt.c
3eb3db
index c7d7bfe74..2f57df0ff 100644
3eb3db
--- a/src/lib/krb5/krb/get_in_tkt.c
3eb3db
+++ b/src/lib/krb5/krb/get_in_tkt.c
3eb3db
@@ -1495,6 +1495,116 @@ accept_method_data(krb5_context context, krb5_init_creds_context ctx)
3eb3db
                                      ctx->method_padata);
3eb3db
 }
3eb3db
 
3eb3db
+/* Return the password expiry time indicated by enc_part2.  Set *is_last_req
3eb3db
+ * if the information came from a last_req value. */
3eb3db
+static void
3eb3db
+get_expiry_times(krb5_enc_kdc_rep_part *enc_part2, krb5_timestamp *pw_exp,
3eb3db
+                 krb5_timestamp *acct_exp, krb5_boolean *is_last_req)
3eb3db
+{
3eb3db
+    krb5_last_req_entry **last_req;
3eb3db
+    krb5_int32 lr_type;
3eb3db
+
3eb3db
+    *pw_exp = 0;
3eb3db
+    *acct_exp = 0;
3eb3db
+    *is_last_req = FALSE;
3eb3db
+
3eb3db
+    /* Look for last-req entries for password or account expiration. */
3eb3db
+    if (enc_part2->last_req) {
3eb3db
+        for (last_req = enc_part2->last_req; *last_req; last_req++) {
3eb3db
+            lr_type = (*last_req)->lr_type;
3eb3db
+            if (lr_type == KRB5_LRQ_ALL_PW_EXPTIME ||
3eb3db
+                lr_type == KRB5_LRQ_ONE_PW_EXPTIME) {
3eb3db
+                *is_last_req = TRUE;
3eb3db
+                *pw_exp = (*last_req)->value;
3eb3db
+            } else if (lr_type == KRB5_LRQ_ALL_ACCT_EXPTIME ||
3eb3db
+                       lr_type == KRB5_LRQ_ONE_ACCT_EXPTIME) {
3eb3db
+                *is_last_req = TRUE;
3eb3db
+                *acct_exp = (*last_req)->value;
3eb3db
+            }
3eb3db
+        }
3eb3db
+    }
3eb3db
+
3eb3db
+    /* If we didn't find any, use the ambiguous key_exp field. */
3eb3db
+    if (*is_last_req == FALSE)
3eb3db
+        *pw_exp = enc_part2->key_exp;
3eb3db
+}
3eb3db
+
3eb3db
+/*
3eb3db
+ * Send an appropriate warning prompter if as_reply indicates that the password
3eb3db
+ * is going to expire soon.  If an expire callback was provided, use that
3eb3db
+ * instead.
3eb3db
+ */
3eb3db
+static void
3eb3db
+warn_pw_expiry(krb5_context context, krb5_get_init_creds_opt *options,
3eb3db
+               krb5_prompter_fct prompter, void *data,
3eb3db
+               const char *in_tkt_service, krb5_kdc_rep *as_reply)
3eb3db
+{
3eb3db
+    krb5_error_code ret;
3eb3db
+    krb5_expire_callback_func expire_cb;
3eb3db
+    void *expire_data;
3eb3db
+    krb5_timestamp pw_exp, acct_exp, now;
3eb3db
+    krb5_boolean is_last_req;
3eb3db
+    krb5_deltat delta;
3eb3db
+    char ts[256], banner[1024];
3eb3db
+
3eb3db
+    if (as_reply == NULL || as_reply->enc_part2 == NULL)
3eb3db
+        return;
3eb3db
+
3eb3db
+    get_expiry_times(as_reply->enc_part2, &pw_exp, &acct_exp, &is_last_req);
3eb3db
+
3eb3db
+    k5_gic_opt_get_expire_cb(options, &expire_cb, &expire_data);
3eb3db
+    if (expire_cb != NULL) {
3eb3db
+        /* Invoke the expire callback and don't send prompter warnings. */
3eb3db
+        (*expire_cb)(context, expire_data, pw_exp, acct_exp, is_last_req);
3eb3db
+        return;
3eb3db
+    }
3eb3db
+
3eb3db
+    /* Don't warn if no password expiry value was sent. */
3eb3db
+    if (pw_exp == 0)
3eb3db
+        return;
3eb3db
+
3eb3db
+    /* Don't warn if the password is being changed. */
3eb3db
+    if (in_tkt_service && strcmp(in_tkt_service, "kadmin/changepw") == 0)
3eb3db
+        return;
3eb3db
+
3eb3db
+    /*
3eb3db
+     * If the expiry time came from a last_req field, assume the KDC wants us
3eb3db
+     * to warn.  Otherwise, warn only if the expiry time is less than a week
3eb3db
+     * from now.
3eb3db
+     */
3eb3db
+    ret = krb5_timeofday(context, &now;;
3eb3db
+    if (ret != 0)
3eb3db
+        return;
3eb3db
+    if (!is_last_req &&
3eb3db
+        (ts_after(now, pw_exp) || ts_delta(pw_exp, now) > 7 * 24 * 60 * 60))
3eb3db
+        return;
3eb3db
+
3eb3db
+    if (!prompter)
3eb3db
+        return;
3eb3db
+
3eb3db
+    ret = krb5_timestamp_to_string(pw_exp, ts, sizeof(ts));
3eb3db
+    if (ret != 0)
3eb3db
+        return;
3eb3db
+
3eb3db
+    delta = ts_delta(pw_exp, now);
3eb3db
+    if (delta < 3600) {
3eb3db
+        snprintf(banner, sizeof(banner),
3eb3db
+                 _("Warning: Your password will expire in less than one hour "
3eb3db
+                   "on %s"), ts);
3eb3db
+    } else if (delta < 86400 * 2) {
3eb3db
+        snprintf(banner, sizeof(banner),
3eb3db
+                 _("Warning: Your password will expire in %d hour%s on %s"),
3eb3db
+                 delta / 3600, delta < 7200 ? "" : "s", ts);
3eb3db
+    } else {
3eb3db
+        snprintf(banner, sizeof(banner),
3eb3db
+                 _("Warning: Your password will expire in %d days on %s"),
3eb3db
+                 delta / 86400, ts);
3eb3db
+    }
3eb3db
+
3eb3db
+    /* PROMPTER_INVOCATION */
3eb3db
+    (*prompter)(context, data, 0, banner, 0, 0);
3eb3db
+}
3eb3db
+
3eb3db
 static krb5_error_code
3eb3db
 init_creds_step_reply(krb5_context context,
3eb3db
                       krb5_init_creds_context ctx,
3eb3db
@@ -1700,6 +1810,8 @@ init_creds_step_reply(krb5_context context,
3eb3db
 
3eb3db
     /* success */
3eb3db
     ctx->complete = TRUE;
3eb3db
+    warn_pw_expiry(context, ctx->opt, ctx->prompter, ctx->prompter_data,
3eb3db
+                   ctx->in_tkt_service, ctx->reply);
3eb3db
 
3eb3db
 cleanup:
3eb3db
     krb5_free_pa_data(context, kdc_padata);
3eb3db
diff --git a/src/lib/krb5/krb/gic_pwd.c b/src/lib/krb5/krb/gic_pwd.c
3eb3db
index 3565a7c4c..f9befac3e 100644
3eb3db
--- a/src/lib/krb5/krb/gic_pwd.c
3eb3db
+++ b/src/lib/krb5/krb/gic_pwd.c
3eb3db
@@ -133,113 +133,6 @@ krb5_init_creds_set_password(krb5_context context,
3eb3db
     return 0;
3eb3db
 }
3eb3db
 
3eb3db
-/* Return the password expiry time indicated by enc_part2.  Set *is_last_req
3eb3db
- * if the information came from a last_req value. */
3eb3db
-static void
3eb3db
-get_expiry_times(krb5_enc_kdc_rep_part *enc_part2, krb5_timestamp *pw_exp,
3eb3db
-                 krb5_timestamp *acct_exp, krb5_boolean *is_last_req)
3eb3db
-{
3eb3db
-    krb5_last_req_entry **last_req;
3eb3db
-    krb5_int32 lr_type;
3eb3db
-
3eb3db
-    *pw_exp = 0;
3eb3db
-    *acct_exp = 0;
3eb3db
-    *is_last_req = FALSE;
3eb3db
-
3eb3db
-    /* Look for last-req entries for password or account expiration. */
3eb3db
-    if (enc_part2->last_req) {
3eb3db
-        for (last_req = enc_part2->last_req; *last_req; last_req++) {
3eb3db
-            lr_type = (*last_req)->lr_type;
3eb3db
-            if (lr_type == KRB5_LRQ_ALL_PW_EXPTIME ||
3eb3db
-                lr_type == KRB5_LRQ_ONE_PW_EXPTIME) {
3eb3db
-                *is_last_req = TRUE;
3eb3db
-                *pw_exp = (*last_req)->value;
3eb3db
-            } else if (lr_type == KRB5_LRQ_ALL_ACCT_EXPTIME ||
3eb3db
-                       lr_type == KRB5_LRQ_ONE_ACCT_EXPTIME) {
3eb3db
-                *is_last_req = TRUE;
3eb3db
-                *acct_exp = (*last_req)->value;
3eb3db
-            }
3eb3db
-        }
3eb3db
-    }
3eb3db
-
3eb3db
-    /* If we didn't find any, use the ambiguous key_exp field. */
3eb3db
-    if (*is_last_req == FALSE)
3eb3db
-        *pw_exp = enc_part2->key_exp;
3eb3db
-}
3eb3db
-
3eb3db
-/*
3eb3db
- * Send an appropriate warning prompter if as_reply indicates that the password
3eb3db
- * is going to expire soon.  If an expire callback was provided, use that
3eb3db
- * instead.
3eb3db
- */
3eb3db
-static void
3eb3db
-warn_pw_expiry(krb5_context context, krb5_get_init_creds_opt *options,
3eb3db
-               krb5_prompter_fct prompter, void *data,
3eb3db
-               const char *in_tkt_service, krb5_kdc_rep *as_reply)
3eb3db
-{
3eb3db
-    krb5_error_code ret;
3eb3db
-    krb5_expire_callback_func expire_cb;
3eb3db
-    void *expire_data;
3eb3db
-    krb5_timestamp pw_exp, acct_exp, now;
3eb3db
-    krb5_boolean is_last_req;
3eb3db
-    krb5_deltat delta;
3eb3db
-    char ts[256], banner[1024];
3eb3db
-
3eb3db
-    get_expiry_times(as_reply->enc_part2, &pw_exp, &acct_exp, &is_last_req);
3eb3db
-
3eb3db
-    k5_gic_opt_get_expire_cb(options, &expire_cb, &expire_data);
3eb3db
-    if (expire_cb != NULL) {
3eb3db
-        /* Invoke the expire callback and don't send prompter warnings. */
3eb3db
-        (*expire_cb)(context, expire_data, pw_exp, acct_exp, is_last_req);
3eb3db
-        return;
3eb3db
-    }
3eb3db
-
3eb3db
-    /* Don't warn if no password expiry value was sent. */
3eb3db
-    if (pw_exp == 0)
3eb3db
-        return;
3eb3db
-
3eb3db
-    /* Don't warn if the password is being changed. */
3eb3db
-    if (in_tkt_service && strcmp(in_tkt_service, "kadmin/changepw") == 0)
3eb3db
-        return;
3eb3db
-
3eb3db
-    /*
3eb3db
-     * If the expiry time came from a last_req field, assume the KDC wants us
3eb3db
-     * to warn.  Otherwise, warn only if the expiry time is less than a week
3eb3db
-     * from now.
3eb3db
-     */
3eb3db
-    ret = krb5_timeofday(context, &now;;
3eb3db
-    if (ret != 0)
3eb3db
-        return;
3eb3db
-    if (!is_last_req &&
3eb3db
-        (ts_after(now, pw_exp) || ts_delta(pw_exp, now) > 7 * 24 * 60 * 60))
3eb3db
-        return;
3eb3db
-
3eb3db
-    if (!prompter)
3eb3db
-        return;
3eb3db
-
3eb3db
-    ret = krb5_timestamp_to_string(pw_exp, ts, sizeof(ts));
3eb3db
-    if (ret != 0)
3eb3db
-        return;
3eb3db
-
3eb3db
-    delta = ts_delta(pw_exp, now);
3eb3db
-    if (delta < 3600) {
3eb3db
-        snprintf(banner, sizeof(banner),
3eb3db
-                 _("Warning: Your password will expire in less than one hour "
3eb3db
-                   "on %s"), ts);
3eb3db
-    } else if (delta < 86400*2) {
3eb3db
-        snprintf(banner, sizeof(banner),
3eb3db
-                 _("Warning: Your password will expire in %d hour%s on %s"),
3eb3db
-                 delta / 3600, delta < 7200 ? "" : "s", ts);
3eb3db
-    } else {
3eb3db
-        snprintf(banner, sizeof(banner),
3eb3db
-                 _("Warning: Your password will expire in %d days on %s"),
3eb3db
-                 delta / 86400, ts);
3eb3db
-    }
3eb3db
-
3eb3db
-    /* PROMPTER_INVOCATION */
3eb3db
-    (*prompter)(context, data, 0, banner, 0, 0);
3eb3db
-}
3eb3db
-
3eb3db
 /*
3eb3db
  * Create a temporary options structure for getting a kadmin/changepw ticket,
3eb3db
  * based on the appplication-specified options.  Propagate all application
3eb3db
@@ -496,9 +389,6 @@ krb5_get_init_creds_password(krb5_context context,
3eb3db
         goto cleanup;
3eb3db
 
3eb3db
 cleanup:
3eb3db
-    if (ret == 0)
3eb3db
-        warn_pw_expiry(context, options, prompter, data, in_tkt_service,
3eb3db
-                       as_reply);
3eb3db
     free(chpw_opts);
3eb3db
     zapfree(gakpw.storage.data, gakpw.storage.length);
3eb3db
     memset(pw0array, 0, sizeof(pw0array));
3eb3db
diff --git a/src/lib/krb5/krb/t_expire_warn.c b/src/lib/krb5/krb/t_expire_warn.c
3eb3db
index 1e59acba1..dc8dc8fb3 100644
3eb3db
--- a/src/lib/krb5/krb/t_expire_warn.c
3eb3db
+++ b/src/lib/krb5/krb/t_expire_warn.c
3eb3db
@@ -28,6 +28,13 @@
3eb3db
 
3eb3db
 static int exp_dummy, prompt_dummy;
3eb3db
 
3eb3db
+static void
3eb3db
+check(krb5_error_code code)
3eb3db
+{
3eb3db
+    if (code != 0)
3eb3db
+        abort();
3eb3db
+}
3eb3db
+
3eb3db
 static krb5_error_code
3eb3db
 prompter_cb(krb5_context ctx, void *data, const char *name,
3eb3db
             const char *banner, int num_prompts, krb5_prompt prompts[])
3eb3db
@@ -52,36 +59,48 @@ int
3eb3db
 main(int argc, char **argv)
3eb3db
 {
3eb3db
     krb5_context ctx;
3eb3db
+    krb5_init_creds_context icctx;
3eb3db
     krb5_get_init_creds_opt *opt;
3eb3db
     char *user, *password, *service = NULL;
3eb3db
-    krb5_boolean use_cb;
3eb3db
+    krb5_boolean use_cb, stepwise;
3eb3db
     krb5_principal client;
3eb3db
     krb5_creds creds;
3eb3db
 
3eb3db
-    if (argc < 4) {
3eb3db
-        fprintf(stderr, "Usage: %s username password {1|0} [service]\n",
3eb3db
+    if (argc < 5) {
3eb3db
+        fprintf(stderr, "Usage: %s username password {1|0} {1|0} [service]\n",
3eb3db
                 argv[0]);
3eb3db
         return 1;
3eb3db
     }
3eb3db
     user = argv[1];
3eb3db
     password = argv[2];
3eb3db
     use_cb = atoi(argv[3]);
3eb3db
-    if (argc >= 5)
3eb3db
-        service = argv[4];
3eb3db
+    stepwise = atoi(argv[4]);
3eb3db
+    if (argc >= 6)
3eb3db
+        service = argv[5];
3eb3db
 
3eb3db
-    assert(krb5_init_context(&ctx) == 0);
3eb3db
-    assert(krb5_get_init_creds_opt_alloc(ctx, &opt) == 0);
3eb3db
+    check(krb5_init_context(&ctx));
3eb3db
+    check(krb5_get_init_creds_opt_alloc(ctx, &opt));
3eb3db
     if (use_cb) {
3eb3db
-        assert(krb5_get_init_creds_opt_set_expire_callback(ctx, opt, expire_cb,
3eb3db
-                                                           &exp_dummy) == 0);
3eb3db
+        check(krb5_get_init_creds_opt_set_expire_callback(ctx, opt, expire_cb,
3eb3db
+                                                          &exp_dummy));
3eb3db
+    }
3eb3db
+    check(krb5_parse_name(ctx, user, &client));
3eb3db
+    if (stepwise) {
3eb3db
+        check(krb5_init_creds_init(ctx, client, prompter_cb, &prompt_dummy, 0,
3eb3db
+                                   opt, &icctx));
3eb3db
+        krb5_init_creds_set_password(ctx, icctx, password);
3eb3db
+        if (service != NULL)
3eb3db
+            check(krb5_init_creds_set_service(ctx, icctx, service));
3eb3db
+        check(krb5_init_creds_get(ctx, icctx));
3eb3db
+        krb5_init_creds_free(ctx, icctx);
3eb3db
+    } else {
3eb3db
+        check(krb5_get_init_creds_password(ctx, &creds, client, password,
3eb3db
+                                           prompter_cb, &prompt_dummy, 0,
3eb3db
+                                           service, opt));
3eb3db
+        krb5_free_cred_contents(ctx, &creds);
3eb3db
     }
3eb3db
-    assert(krb5_parse_name(ctx, user, &client) == 0);
3eb3db
-    assert(krb5_get_init_creds_password(ctx, &creds, client, password,
3eb3db
-                                        prompter_cb, &prompt_dummy, 0, service,
3eb3db
-                                        opt) == 0);
3eb3db
     krb5_get_init_creds_opt_free(ctx, opt);
3eb3db
     krb5_free_principal(ctx, client);
3eb3db
-    krb5_free_cred_contents(ctx, &creds);
3eb3db
     krb5_free_context(ctx);
3eb3db
     return 0;
3eb3db
 }
3eb3db
diff --git a/src/lib/krb5/krb/t_expire_warn.py b/src/lib/krb5/krb/t_expire_warn.py
3eb3db
index e021379ab..d4926d67d 100755
3eb3db
--- a/src/lib/krb5/krb/t_expire_warn.py
3eb3db
+++ b/src/lib/krb5/krb/t_expire_warn.py
3eb3db
@@ -36,28 +36,33 @@ realm.run([kadminl, 'addprinc', '-pw', 'pass', '-pwexpire', '12 hours',
3eb3db
 realm.run([kadminl, 'addprinc', '-pw', 'pass', '-pwexpire', '3 days', 'days'])
3eb3db
 
3eb3db
 # Check for expected prompter warnings when no expire callback is used.
3eb3db
-output = realm.run(['./t_expire_warn', 'noexpire', 'pass', '0'])
3eb3db
+output = realm.run(['./t_expire_warn', 'noexpire', 'pass', '0', '0'])
3eb3db
 if output:
3eb3db
     fail('Unexpected output for noexpire')
3eb3db
-output = realm.run(['./t_expire_warn', 'minutes', 'pass', '0'])
3eb3db
-if ' less than one hour on ' not in output:
3eb3db
-    fail('Expected warning not seen for minutes')
3eb3db
-output = realm.run(['./t_expire_warn', 'hours', 'pass', '0'])
3eb3db
-if ' hours on ' not in output:
3eb3db
-    fail('Expected warning not seen for hours')
3eb3db
-output = realm.run(['./t_expire_warn', 'days', 'pass', '0'])
3eb3db
-if ' days on ' not in output:
3eb3db
-    fail('Expected warning not seen for days')
3eb3db
+realm.run(['./t_expire_warn', 'minutes', 'pass', '0', '0'],
3eb3db
+          expected_msg=' less than one hour on ')
3eb3db
+realm.run(['./t_expire_warn', 'hours', 'pass', '0', '0'],
3eb3db
+          expected_msg=' hours on ')
3eb3db
+realm.run(['./t_expire_warn', 'days', 'pass', '0', '0'],
3eb3db
+          expected_msg=' days on ')
3eb3db
+# Try one case with the stepwise interface.
3eb3db
+realm.run(['./t_expire_warn', 'days', 'pass', '0', '1'],
3eb3db
+          expected_msg=' days on ')
3eb3db
 
3eb3db
 # Check for expected expire callback behavior.  These tests are
3eb3db
 # carefully agnostic about whether the KDC supports last_req fields,
3eb3db
 # and could be made more specific if last_req support is added.
3eb3db
-output = realm.run(['./t_expire_warn', 'noexpire', 'pass', '1'])
3eb3db
+output = realm.run(['./t_expire_warn', 'noexpire', 'pass', '1', '0'])
3eb3db
 if 'password_expiration = 0\n' not in output or \
3eb3db
         'account_expiration = 0\n' not in output or \
3eb3db
         'is_last_req = ' not in output:
3eb3db
     fail('Expected callback output not seen for noexpire')
3eb3db
-output = realm.run(['./t_expire_warn', 'days', 'pass', '1'])
3eb3db
+output = realm.run(['./t_expire_warn', 'days', 'pass', '1', '0'])
3eb3db
+if 'password_expiration = ' not in output or \
3eb3db
+        'password_expiration = 0\n' in output:
3eb3db
+    fail('Expected non-zero password expiration not seen for days')
3eb3db
+# Try one case with the stepwise interface.
3eb3db
+output = realm.run(['./t_expire_warn', 'days', 'pass', '1', '1'])
3eb3db
 if 'password_expiration = ' not in output or \
3eb3db
         'password_expiration = 0\n' in output:
3eb3db
     fail('Expected non-zero password expiration not seen for days')