>From 0e0c313cc9fb3a7b1d2c59ac4530bcd0791c8eec Mon Sep 17 00:00:00 2001
From: Simo Sorce <simo@redhat.com>
Date: Thu, 22 Nov 2012 16:34:18 -0500
Subject: [PATCH] Cleanup error message handling for krb5 child

Use the new internal SSSD errors, to simplify error handling.
Instead of using up to 3 different error types (system, krb5 and
pam_status), collapse all error reporting into one error type mapped
on errno_t.

The returned error can contain either SSSD internal errors, kerberos
errors or system errors, they all use different number spaces so there
is no overlap and they can be safely merged.

This means that errors being sent from the child to the parent are not
pam status error messages anymore.
The callers have been changed to properly deal with that.

Also note that this patch removes returning SSS_PAM_SYSTEM_INFO from
the krb5_child for kerberos errors as all it was doing was simply to
make the parent emit the same debug log already emitted by the child,
and the code is simpler if we do not do that.
---
 src/providers/krb5/krb5_auth.c  | 157 ++++++++--------
 src/providers/krb5/krb5_child.c | 389 +++++++++++++++++-----------------------
 src/util/util_errors.c          |   7 +-
 src/util/util_errors.h          |   5 +
 4 files changed, 256 insertions(+), 302 deletions(-)

diff --git a/src/providers/krb5/krb5_auth.c b/src/providers/krb5/krb5_auth.c
index 0b56f3a5a377518d648faeb6fa261608ae9a1c40..e41e1a1e847a14e90d62c25b1bf9e5b4b1f59ce0 100644
--- a/src/providers/krb5/krb5_auth.c
+++ b/src/providers/krb5/krb5_auth.c
@@ -927,22 +927,64 @@ static void krb5_auth_done(struct tevent_req *subreq)
 
     /* If the child request failed, but did not return an offline error code,
      * return with the status */
-    if (res->msg_status != PAM_SUCCESS &&
-        res->msg_status != PAM_AUTHINFO_UNAVAIL &&
-        res->msg_status != PAM_AUTHTOK_LOCK_BUSY &&
-        res->msg_status != PAM_NEW_AUTHTOK_REQD) {
-        state->pam_status = res->msg_status;
-        state->dp_err = DP_ERR_OK;
-        ret = EOK;
-        goto done;
-    } else {
-        state->pam_status = res->msg_status;
-    }
+    switch (res->msg_status) {
+    case ERR_OK:
+        /* If the child request was successful and we run the first pass of the
+         * change password request just return success. */
+        if (pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM) {
+            state->pam_status = PAM_SUCCESS;
+            state->dp_err = DP_ERR_OK;
+            ret = EOK;
+            goto done;
+        }
+        break;
 
-    /* If the password is expired we can safely remove the ccache from the
-     * cache and disk if it is not actively used anymore. This will allow to
-     * create a new random ccache if sshd with privilege separation is used. */
-    if (res->msg_status == PAM_NEW_AUTHTOK_REQD) {
+    case ERR_NETWORK_IO:
+        if (kr->kpasswd_srv != NULL &&
+            (pd->cmd == SSS_PAM_CHAUTHTOK ||
+             pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM)) {
+            /* if using a dedicated kpasswd server for a chpass operation... */
+
+            be_fo_set_port_status(state->be_ctx,
+                                  state->krb5_ctx->kpasswd_service->name,
+                                  kr->kpasswd_srv, PORT_NOT_WORKING);
+            /* ..try to resolve next kpasswd server */
+            state->search_kpasswd = true;
+            subreq = be_resolve_server_send(state, state->ev, state->be_ctx,
+                                state->krb5_ctx->kpasswd_service->name,
+                                state->kr->kpasswd_srv == NULL ?  true : false);
+            if (subreq == NULL) {
+                DEBUG(1, ("Resolver request failed.\n"));
+                ret = ENOMEM;
+                goto done;
+            }
+            tevent_req_set_callback(subreq, krb5_auth_resolve_done, req);
+            return;
+        } else if (kr->srv != NULL) {
+            /* failed to use the KDC... */
+            be_fo_set_port_status(state->be_ctx,
+                                  state->krb5_ctx->service->name,
+                                  kr->srv, PORT_NOT_WORKING);
+            /* ..try to resolve next KDC */
+            state->search_kpasswd = false;
+            subreq = be_resolve_server_send(state, state->ev, state->be_ctx,
+                                            state->krb5_ctx->service->name,
+                                            kr->srv == NULL ?  true : false);
+            if (subreq == NULL) {
+                DEBUG(1, ("Resolver request failed.\n"));
+                ret = ENOMEM;
+                goto done;
+            }
+            tevent_req_set_callback(subreq, krb5_auth_resolve_done, req);
+            return;
+        }
+        break;
+
+    case ERR_CREDS_EXPIRED:
+        /* If the password is expired we can safely remove the ccache from the
+         * cache and disk if it is not actively used anymore. This will allow
+         * to create a new random ccache if sshd with privilege separation is
+         * used. */
         if (pd->cmd == SSS_PAM_AUTHENTICATE && !kr->active_ccache) {
             if (kr->old_ccname != NULL) {
                 ret = safe_remove_old_ccache_file(kr->cc_be, kr->upn,
@@ -960,70 +1002,39 @@ static void krb5_auth_done(struct tevent_req *subreq)
             }
         }
 
-        state->pam_status = res->msg_status;
+        state->pam_status = PAM_NEW_AUTHTOK_REQD;
+        state->dp_err = DP_ERR_OK;
+        ret = EOK;
+        goto done;
+
+    case ERR_NO_CREDS:
+        state->pam_status = PAM_CRED_UNAVAIL;
+        state->dp_err = DP_ERR_OK;
+        ret = EOK;
+        goto done;
+
+    case ERR_AUTH_FAILED:
+        state->pam_status = PAM_AUTH_ERR;
         state->dp_err = DP_ERR_OK;
         ret = EOK;
         goto done;
-    }
 
-    /* If the child request was successful and we run the first pass of the
-     * change password request just return success. */
-    if (res->msg_status == PAM_SUCCESS && pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM) {
-        state->pam_status = PAM_SUCCESS;
+    default:
+        state->pam_status = PAM_SYSTEM_ERR;
         state->dp_err = DP_ERR_OK;
         ret = EOK;
         goto done;
     }
 
-    /* if using a dedicated kpasswd server for a chpass operation... */
     if (kr->kpasswd_srv != NULL &&
-        (pd->cmd == SSS_PAM_CHAUTHTOK || pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM)) {
-        /* ..which is unreachable by now.. */
-        if (res->msg_status == PAM_AUTHTOK_LOCK_BUSY) {
-            be_fo_set_port_status(state->be_ctx,
-                                  state->krb5_ctx->kpasswd_service->name,
-                                  kr->kpasswd_srv, PORT_NOT_WORKING);
-            /* ..try to resolve next kpasswd server */
-            state->search_kpasswd = true;
-            subreq = be_resolve_server_send(state, state->ev, state->be_ctx,
-                                state->krb5_ctx->kpasswd_service->name,
-                                state->kr->kpasswd_srv == NULL ?  true : false);
-            if (subreq == NULL) {
-                DEBUG(1, ("Resolver request failed.\n"));
-                ret = ENOMEM;
-                goto done;
-            }
-            tevent_req_set_callback(subreq, krb5_auth_resolve_done, req);
-            return;
-        } else {
-            be_fo_set_port_status(state->be_ctx,
-                                  state->krb5_ctx->kpasswd_service->name,
-                                  kr->kpasswd_srv, PORT_WORKING);
-        }
-    }
-
-    /* if the KDC for auth (PAM_AUTHINFO_UNAVAIL) or
-     * chpass (PAM_AUTHTOK_LOCK_BUSY) was not available while using KDC
-     * also for chpass operation... */
-    if (res->msg_status == PAM_AUTHINFO_UNAVAIL ||
-        (kr->kpasswd_srv == NULL && res->msg_status == PAM_AUTHTOK_LOCK_BUSY)) {
-        if (kr->srv != NULL) {
-            be_fo_set_port_status(state->be_ctx, state->krb5_ctx->service->name,
-                                  kr->srv, PORT_NOT_WORKING);
-            /* ..try to resolve next KDC */
-            state->search_kpasswd = false;
-            subreq = be_resolve_server_send(state, state->ev, state->be_ctx,
-                                            state->krb5_ctx->service->name,
-                                            kr->srv == NULL ?  true : false);
-            if (subreq == NULL) {
-                DEBUG(1, ("Resolver request failed.\n"));
-                ret = ENOMEM;
-                goto done;
-            }
-            tevent_req_set_callback(subreq, krb5_auth_resolve_done, req);
-            return;
-        }
+        (pd->cmd == SSS_PAM_CHAUTHTOK ||
+         pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM)) {
+        /* found a dedicated kpasswd server for a chpass operation */
+        be_fo_set_port_status(state->be_ctx,
+                              state->krb5_ctx->service->name,
+                              kr->kpasswd_srv, PORT_WORKING);
     } else if (kr->srv != NULL) {
+        /* found a KDC */
         be_fo_set_port_status(state->be_ctx, state->krb5_ctx->service->name,
                               kr->srv, PORT_WORKING);
     }
@@ -1062,11 +1073,13 @@ static void krb5_auth_done(struct tevent_req *subreq)
         goto done;
     }
 
-    if (res->msg_status == PAM_SUCCESS &&
-        dp_opt_get_int(kr->krb5_ctx->opts, KRB5_RENEW_INTERVAL) > 0 &&
-        (pd->cmd == SSS_PAM_AUTHENTICATE || pd->cmd == SSS_CMD_RENEW ||
+    if (res->msg_status == ERR_OK &&
+        (dp_opt_get_int(kr->krb5_ctx->opts, KRB5_RENEW_INTERVAL) > 0) &&
+        (pd->cmd == SSS_PAM_AUTHENTICATE ||
+         pd->cmd == SSS_CMD_RENEW ||
          pd->cmd == SSS_PAM_CHAUTHTOK) &&
-        res->tgtt.renew_till > res->tgtt.endtime && kr->ccname != NULL) {
+        (res->tgtt.renew_till > res->tgtt.endtime) &&
+        (kr->ccname != NULL)) {
         DEBUG(7, ("Adding [%s] for automatic renewal.\n", kr->ccname));
         ret = add_tgt_to_renew_table(kr->krb5_ctx, kr->ccname, &(res->tgtt),
                                      pd, kr->upn);
diff --git a/src/providers/krb5/krb5_child.c b/src/providers/krb5/krb5_child.c
index 42dffe7b83978f1489b782e38a69f1bad3b9257b..9cc0f55484fcb190b62ae6f68718420c5168a66d 100644
--- a/src/providers/krb5/krb5_child.c
+++ b/src/providers/krb5/krb5_child.c
@@ -525,9 +525,11 @@ create_ccache(uid_t uid, gid_t gid, krb5_context ctx,
     return EINVAL;  /* Should never get here */
 }
 
-static errno_t pack_response_packet(struct response *resp, int status,
-                                    struct pam_data *pd)
+static errno_t pack_response_packet(TALLOC_CTX *mem_ctx, errno_t error,
+                                    struct response_data *resp_list,
+                                    uint8_t **_buf, size_t *_len)
 {
+    uint8_t *buf;
     size_t size = 0;
     size_t p = 0;
     struct response_data *pdr;
@@ -544,116 +546,69 @@ static errno_t pack_response_packet(struct response *resp, int status,
 
     size = sizeof(int32_t);
 
-    pdr = pd->resp_list;
-    while (pdr != NULL) {
+    for (pdr = resp_list; pdr != NULL; pdr = pdr->next) {
         size += 2*sizeof(int32_t) + pdr->len;
-        pdr = pdr->next;
     }
 
-    resp->buf = talloc_array(resp, uint8_t, size);
-    if (!resp->buf) {
+    buf = talloc_array(mem_ctx, uint8_t, size);
+    if (!buf) {
         DEBUG(1, ("Insufficient memory to create message.\n"));
         return ENOMEM;
     }
 
-    SAFEALIGN_SET_INT32(&resp->buf[p], status, &p);
+    SAFEALIGN_SET_INT32(&buf[p], error, &p);
 
-    pdr = pd->resp_list;
-    while(pdr != NULL) {
-        SAFEALIGN_SET_INT32(&resp->buf[p], pdr->type, &p);
-        SAFEALIGN_SET_INT32(&resp->buf[p], pdr->len, &p);
-        safealign_memcpy(&resp->buf[p], pdr->data, pdr->len, &p);
-
-        pdr = pdr->next;
+    for (pdr = resp_list; pdr != NULL; pdr = pdr->next) {
+        SAFEALIGN_SET_INT32(&buf[p], pdr->type, &p);
+        SAFEALIGN_SET_INT32(&buf[p], pdr->len, &p);
+        safealign_memcpy(&buf[p], pdr->data, pdr->len, &p);
     }
 
-    resp->size = p;
-
     DEBUG(SSSDBG_TRACE_INTERNAL, ("response packet size: [%d]\n", p));
 
+    *_buf = buf;
+    *_len = p;
     return EOK;
 }
 
-static struct response *prepare_response_message(struct krb5_req *kr,
-                                                 krb5_error_code kerr,
-                                                 int pam_status)
+static errno_t k5c_attach_ccname_msg(struct krb5_req *kr)
 {
     char *msg = NULL;
-    const char *krb5_msg = NULL;
     int ret;
-    struct response *resp;
 
-    resp = talloc_zero(kr, struct response);
-    if (resp == NULL) {
-        DEBUG(1, ("Initializing response failed.\n"));
-        return NULL;
+    if (kr->ccname == NULL) {
+        DEBUG(1, ("Error obtaining ccname.\n"));
+        return ERR_INTERNAL;
     }
 
-    DEBUG(SSSDBG_TRACE_FUNC, ("Building response for result [%d]\n", kerr));
-
-    if (kerr == 0) {
-        if (kr->pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM) {
-            pam_status = PAM_SUCCESS;
-            ret = EOK;
-        } else if (kr->pd->cmd == SSS_PAM_ACCT_MGMT) {
-            ret = EOK;
-        } else {
-            if (kr->ccname == NULL) {
-                DEBUG(1, ("Error obtaining ccname.\n"));
-                return NULL;
-            }
-
-            msg = talloc_asprintf(kr, "%s=%s",CCACHE_ENV_NAME, kr->ccname);
-            if (msg == NULL) {
-                DEBUG(1, ("talloc_asprintf failed.\n"));
-                return NULL;
-            }
-
-            pam_status = PAM_SUCCESS;
-            ret = pam_add_response(kr->pd, SSS_PAM_ENV_ITEM, strlen(msg) + 1,
-                                   (uint8_t *) msg);
-            talloc_zfree(msg);
-        }
-    } else {
-        krb5_msg = sss_krb5_get_error_message(krb5_error_ctx, kerr);
-        if (krb5_msg == NULL) {
-            DEBUG(1, ("sss_krb5_get_error_message failed.\n"));
-            return NULL;
-        }
-
-        ret = pam_add_response(kr->pd, SSS_PAM_SYSTEM_INFO,
-                               strlen(krb5_msg) + 1,
-                               (const uint8_t *) krb5_msg);
-        sss_krb5_free_error_message(krb5_error_ctx, krb5_msg);
-    }
-    if (ret != EOK) {
-        DEBUG(1, ("pam_add_response failed.\n"));
+    msg = talloc_asprintf(kr, "%s=%s",CCACHE_ENV_NAME, kr->ccname);
+    if (msg == NULL) {
+        DEBUG(1, ("talloc_asprintf failed.\n"));
+        return ENOMEM;
     }
 
-    ret = pack_response_packet(resp, pam_status, kr->pd);
-    if (ret != EOK) {
-        DEBUG(1, ("pack_response_packet failed.\n"));
-        return NULL;
-    }
+    ret = pam_add_response(kr->pd, SSS_PAM_ENV_ITEM,
+                           strlen(msg) + 1, (uint8_t *)msg);
+    talloc_zfree(msg);
 
-    return resp;
+    return ret;
 }
 
-static errno_t k5c_send_data(struct krb5_req *kr, int fd,
-                             krb5_error_code kerr, int status)
+static errno_t k5c_send_data(struct krb5_req *kr, int fd, errno_t error)
 {
-    struct response *resp;
     size_t written;
+    uint8_t *buf;
+    size_t len;
     int ret;
 
-    resp = prepare_response_message(kr, kerr, status);
-    if (resp == NULL) {
-        DEBUG(1, ("prepare_response_message failed.\n"));
-        return ENOMEM;
+    ret = pack_response_packet(kr, error, kr->pd->resp_list, &buf, &len);
+    if (ret != EOK) {
+        DEBUG(1, ("pack_response_packet failed.\n"));
+        return ret;
     }
 
     errno = 0;
-    written = sss_atomic_write_s(fd, resp->buf, resp->size);
+    written = sss_atomic_write_s(fd, buf, len);
     if (written == -1) {
         ret = errno;
         DEBUG(SSSDBG_CRIT_FAILURE,
@@ -661,10 +616,10 @@ static errno_t k5c_send_data(struct krb5_req *kr, int fd,
         return ret;
     }
 
-    if (written != resp->size) {
+    if (written != len) {
         DEBUG(SSSDBG_CRIT_FAILURE,
               ("Write error, wrote [%d] bytes, expected [%d]\n",
-               written, resp->size));
+               written, len));
         return EOK;
     }
 
@@ -915,10 +870,9 @@ done:
 static krb5_error_code get_and_save_tgt(struct krb5_req *kr,
                                         const char *password)
 {
-    krb5_error_code kerr = 0;
-    int ret;
     const char *realm_name;
     int realm_length;
+    krb5_error_code kerr;
 
 
     kerr = sss_krb5_get_init_creds_opt_set_expire_callback(kr->ctx, kr->options,
@@ -957,10 +911,10 @@ static krb5_error_code get_and_save_tgt(struct krb5_req *kr,
         /* We drop root privileges which were needed to read the keytab file
          * for the validation of the credentials or for FAST here to run the
          * ccache I/O operations with user privileges. */
-        ret = become_user(kr->uid, kr->gid);
-        if (ret != EOK) {
+        kerr = become_user(kr->uid, kr->gid);
+        if (kerr != 0) {
             DEBUG(1, ("become_user failed.\n"));
-            return ret;
+            return kerr;
         }
     }
 
@@ -973,8 +927,8 @@ static krb5_error_code get_and_save_tgt(struct krb5_req *kr,
         goto done;
     }
 
-    ret = add_ticket_times_and_upn_to_response(kr);
-    if (ret != EOK) {
+    kerr = add_ticket_times_and_upn_to_response(kr);
+    if (kerr != 0) {
         DEBUG(1, ("add_ticket_times_and_upn_to_response failed.\n"));
     }
 
@@ -987,59 +941,46 @@ done:
 
 }
 
-static int kerr_handle_error(krb5_error_code kerr)
+static errno_t map_krb5_error(krb5_error_code kerr)
 {
-    int pam_status;
-
     KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr);
+
     switch (kerr) {
-        case KRB5_LIBOS_CANTREADPWD:
-            pam_status = PAM_CRED_UNAVAIL;
-            break;
-        case KRB5_KDC_UNREACH:
-            pam_status = PAM_AUTHINFO_UNAVAIL;
-            break;
-        case KRB5KDC_ERR_KEY_EXP:
-            pam_status = PAM_NEW_AUTHTOK_REQD;
-            break;
-        case KRB5KRB_AP_ERR_BAD_INTEGRITY:
-            pam_status = PAM_AUTH_ERR;
-            break;
-        case KRB5_PREAUTH_FAILED:
-        case KRB5KDC_ERR_PREAUTH_FAILED:
-            pam_status = PAM_CRED_ERR;
-            break;
-        default:
-            pam_status = PAM_SYSTEM_ERR;
-            break;
-    }
+    case 0:
+        return ERR_OK;
 
-    return pam_status;
-}
+    case KRB5_LIBOS_CANTREADPWD:
+        return ERR_NO_CREDS;
 
-static int kerr_to_status(krb5_error_code kerr)
-{
-    if (kerr == 0) {
-        return PAM_SUCCESS;
-    }
+    case KRB5_KDC_UNREACH:
+        return ERR_NETWORK_IO;
 
-    return kerr_handle_error(kerr);
+    case KRB5KDC_ERR_KEY_EXP:
+        return ERR_CREDS_EXPIRED;
+
+    case KRB5KRB_AP_ERR_BAD_INTEGRITY:
+    case KRB5_PREAUTH_FAILED:
+    case KRB5KDC_ERR_PREAUTH_FAILED:
+        return ERR_AUTH_FAILED;
+
+    default:
+        return ERR_INTERNAL;
+    }
 }
 
-static errno_t changepw_child(struct krb5_req *kr, int *_status)
+static errno_t changepw_child(struct krb5_req *kr, bool prelim)
 {
     int ret;
     krb5_error_code kerr = 0;
     const char *password = NULL;
     const char *newpassword = NULL;
-    int pam_status = PAM_SYSTEM_ERR;
     int result_code = -1;
     krb5_data result_code_string;
     krb5_data result_string;
     char *user_error_message = NULL;
     size_t user_resp_len;
     uint8_t *user_resp;
-    krb5_prompter_fct prompter = sss_krb5_prompter;
+    krb5_prompter_fct prompter = NULL;
     const char *realm_name;
     int realm_length;
     krb5_get_init_creds_opt *chagepw_options;
@@ -1050,20 +991,18 @@ static errno_t changepw_child(struct krb5_req *kr, int *_status)
     if (ret != EOK) {
         DEBUG(1, ("Failed to fetch current password [%d] %s.\n",
                   ret, strerror(ret)));
-        pam_status = PAM_CRED_INSUFFICIENT;
-        kerr = KRB5KRB_ERR_GENERIC;
-        goto done;
+        return ERR_NO_CREDS;
     }
 
-    if (kr->pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM) {
+    if (!prelim) {
         /* We do not need a password expiration warning here. */
-        prompter = NULL;
+        prompter = sss_krb5_prompter;
     }
 
     kerr = get_changepw_options(kr->ctx, &chagepw_options);
     if (kerr != 0) {
         DEBUG(SSSDBG_OP_FAILURE, ("get_changepw_options failed.\n"));
-        goto done;
+        return kerr;
     }
 
     sss_krb5_princ_realm(kr->ctx, kr->princ, &realm_name, &realm_length);
@@ -1077,27 +1016,24 @@ static errno_t changepw_child(struct krb5_req *kr, int *_status)
                                         chagepw_options);
     sss_krb5_get_init_creds_opt_free(kr->ctx, chagepw_options);
     if (kerr != 0) {
-        pam_status = kerr_handle_error(kerr);
-        goto done;
+        return kerr;
     }
 
     sss_authtok_set_empty(&kr->pd->authtok);
 
-    if (kr->pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM) {
+    if (prelim) {
         DEBUG(SSSDBG_TRACE_LIBS,
               ("Initial authentication for change password operation "
                "successful.\n"));
         krb5_free_cred_contents(kr->ctx, kr->creds);
-        pam_status = PAM_SUCCESS;
-        goto done;
+        return EOK;
     }
 
     ret = sss_authtok_get_password(&kr->pd->newauthtok, &newpassword, NULL);
     if (ret != EOK) {
         DEBUG(1, ("Failed to fetch new password [%d] %s.\n",
                   ret, strerror(ret)));
-        kerr = KRB5KRB_ERR_GENERIC;
-        goto done;
+        return ERR_NO_CREDS;
     }
 
     memset(&result_code_string, 0, sizeof(krb5_data));
@@ -1107,15 +1043,12 @@ static errno_t changepw_child(struct krb5_req *kr, int *_status)
                                 &result_code_string, &result_string);
 
     if (kerr == KRB5_KDC_UNREACH) {
-        pam_status = PAM_AUTHTOK_LOCK_BUSY;
-        goto done;
+        return ERR_NETWORK_IO;
     }
 
     if (kerr != 0 || result_code != 0) {
         if (kerr != 0) {
             KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr);
-        } else {
-            kerr = KRB5KRB_ERR_GENERIC;
         }
 
         if (result_code_string.length > 0) {
@@ -1153,8 +1086,7 @@ static errno_t changepw_child(struct krb5_req *kr, int *_status)
             }
         }
 
-        pam_status = PAM_AUTHTOK_ERR;
-        goto done;
+        return ERR_CHPASS_FAILED;
     }
 
     krb5_free_cred_contents(kr->ctx, kr->creds);
@@ -1163,88 +1095,90 @@ static errno_t changepw_child(struct krb5_req *kr, int *_status)
 
     sss_authtok_set_empty(&kr->pd->newauthtok);
 
-    pam_status = kerr_to_status(kerr);
-
-done:
-    *_status = pam_status;
-    return kerr;
+    if (kerr == 0) {
+        kerr = k5c_attach_ccname_msg(kr);
+    }
+    return map_krb5_error(kerr);
 }
 
-static errno_t tgt_req_child(struct krb5_req *kr, int *_status)
+static errno_t tgt_req_child(struct krb5_req *kr)
 {
-    int ret;
-    krb5_error_code kerr = 0;
-    const char *password = NULL;
-    int pam_status = PAM_SYSTEM_ERR;
     krb5_get_init_creds_opt *chagepw_options;
+    const char *password = NULL;
+    krb5_error_code kerr;
+    int ret;
 
     DEBUG(SSSDBG_TRACE_LIBS, ("Attempting to get a TGT\n"));
 
     ret = sss_authtok_get_password(&kr->pd->authtok, &password, NULL);
-    if (ret != EOK) {
-        DEBUG(SSSDBG_OP_FAILURE, ("Unknown authtok type\n"));
-        pam_status = PAM_CRED_INSUFFICIENT;
-        kerr = KRB5KRB_ERR_GENERIC;
-        goto done;
+    switch (ret) {
+        if (ret == EACCES) {
+            DEBUG(SSSDBG_OP_FAILURE, ("Invalid authtok type\n"));
+            return ERR_INVALID_CRED_TYPE;
+        }
+        DEBUG(SSSDBG_OP_FAILURE, ("No credentials available\n"));
+        return ERR_NO_CREDS;
     }
 
     kerr = get_and_save_tgt(kr, password);
 
+    if (kerr != KRB5KDC_ERR_KEY_EXP) {
+        if (kerr == 0) {
+            kerr = k5c_attach_ccname_msg(kr);
+        }
+        ret = map_krb5_error(kerr);
+        goto done;
+    }
+
     /* If the password is expired the KDC will always return
        KRB5KDC_ERR_KEY_EXP regardless if the supplied password is correct or
        not. In general the password can still be used to get a changepw ticket.
        So we validate the password by trying to get a changepw ticket. */
-    if (kerr == KRB5KDC_ERR_KEY_EXP) {
-        DEBUG(SSSDBG_TRACE_LIBS, ("Password was expired\n"));
-        kerr = sss_krb5_get_init_creds_opt_set_expire_callback(kr->ctx,
-                                                               kr->options,
-                                                               NULL, NULL);
-        if (kerr != 0) {
-            KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr);
-            DEBUG(1, ("Failed to unset expire callback, continue ...\n"));
-        }
+    DEBUG(SSSDBG_TRACE_LIBS, ("Password was expired\n"));
+    kerr = sss_krb5_get_init_creds_opt_set_expire_callback(kr->ctx,
+                                                           kr->options,
+                                                           NULL, NULL);
+    if (kerr != 0) {
+        KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr);
+        DEBUG(1, ("Failed to unset expire callback, continue ...\n"));
+    }
 
-        kerr = get_changepw_options(kr->ctx, &chagepw_options);
-        if (kerr != 0) {
-            DEBUG(SSSDBG_OP_FAILURE, ("get_changepw_options failed.\n"));
-            goto done;
-        }
+    kerr = get_changepw_options(kr->ctx, &chagepw_options);
+    if (kerr != 0) {
+        DEBUG(SSSDBG_OP_FAILURE, ("get_changepw_options failed.\n"));
+        return kerr;
+    }
 
-        kerr = krb5_get_init_creds_password(kr->ctx, kr->creds, kr->princ,
-                                            discard_const(password),
-                                            sss_krb5_prompter, kr, 0,
-                                            SSSD_KRB5_CHANGEPW_PRINCIPAL,
-                                            chagepw_options);
+    kerr = krb5_get_init_creds_password(kr->ctx, kr->creds, kr->princ,
+                                        discard_const(password),
+                                        sss_krb5_prompter, kr, 0,
+                                        SSSD_KRB5_CHANGEPW_PRINCIPAL,
+                                        chagepw_options);
 
-        sss_krb5_get_init_creds_opt_free(kr->ctx, chagepw_options);
+    sss_krb5_get_init_creds_opt_free(kr->ctx, chagepw_options);
 
-        krb5_free_cred_contents(kr->ctx, kr->creds);
-        if (kerr == 0) {
-            kerr = KRB5KDC_ERR_KEY_EXP;
-        }
+    krb5_free_cred_contents(kr->ctx, kr->creds);
+    if (kerr == 0) {
+        ret = ERR_CREDS_EXPIRED;
+    } else {
+        ret = map_krb5_error(kerr);
     }
 
-    sss_authtok_set_empty(&kr->pd->authtok);
-
-    pam_status = kerr_to_status(kerr);
-
 done:
-    *_status = pam_status;
-    return kerr;
+    sss_authtok_set_empty(&kr->pd->authtok);
+    return ret;
 }
 
-static errno_t kuserok_child(struct krb5_req *kr, int *_status)
+static errno_t kuserok_child(struct krb5_req *kr)
 {
     krb5_boolean access_allowed;
-    int ret;
     krb5_error_code kerr;
 
     DEBUG(SSSDBG_TRACE_LIBS, ("Verifying if principal can log in as user\n"));
 
     /* krb5_kuserok tries to verify that kr->pd->user is a locally known
      * account, so we have to unset _SSS_LOOPS to make getpwnam() work. */
-    ret = unsetenv("_SSS_LOOPS");
-    if (ret != EOK) {
+    if (unsetenv("_SSS_LOOPS") != 0) {
         DEBUG(1, ("Failed to unset _SSS_LOOPS, "
                   "krb5_kuserok will most certainly fail.\n"));
     }
@@ -1259,17 +1193,19 @@ static errno_t kuserok_child(struct krb5_req *kr, int *_status)
     DEBUG(SSSDBG_TRACE_LIBS,
           ("Access was %s\n", access_allowed ? "allowed" : "denied"));
 
-    *_status = access_allowed ? 0 : 1;
-    return kerr;
+    if (access_allowed) {
+        return EOK;
+    }
+
+    return ERR_AUTH_DENIED;
 }
 
-static errno_t renew_tgt_child(struct krb5_req *kr, int *_status)
+static errno_t renew_tgt_child(struct krb5_req *kr)
 {
-    int ret;
-    int status = PAM_AUTHTOK_ERR;
-    int kerr;
     const char *ccname;
     krb5_ccache ccache = NULL;
+    krb5_error_code kerr;
+    int ret;
 
     DEBUG(SSSDBG_TRACE_LIBS, ("Renewing a ticket\n"));
 
@@ -1278,8 +1214,7 @@ static errno_t renew_tgt_child(struct krb5_req *kr, int *_status)
         DEBUG(SSSDBG_OP_FAILURE,
               ("Unsupported authtok type for TGT renewal [%d].\n",
                sss_authtok_get_type(&kr->pd->authtok)));
-        kerr = EINVAL;
-        goto done;
+        return ERR_INVALID_CRED_TYPE;
     }
 
     kerr = krb5_cc_resolve(kr->ctx, ccname, &ccache);
@@ -1290,7 +1225,6 @@ static errno_t renew_tgt_child(struct krb5_req *kr, int *_status)
 
     kerr = krb5_get_renewed_creds(kr->ctx, kr->creds, kr->princ, ccache, NULL);
     if (kerr != 0) {
-        status = kerr_handle_error(kerr);
         goto done;
     }
 
@@ -1309,10 +1243,9 @@ static errno_t renew_tgt_child(struct krb5_req *kr, int *_status)
         /* We drop root privileges which were needed to read the keytab file
          * for the validation of the credentials or for FAST here to run the
          * ccache I/O operations with user privileges. */
-        ret = become_user(kr->uid, kr->gid);
-        if (ret != EOK) {
+        kerr = become_user(kr->uid, kr->gid);
+        if (kerr != 0) {
             DEBUG(1, ("become_user failed.\n"));
-            kerr = ret;
             goto done;
         }
     }
@@ -1329,13 +1262,12 @@ static errno_t renew_tgt_child(struct krb5_req *kr, int *_status)
         goto done;
     }
 
-    ret = add_ticket_times_and_upn_to_response(kr);
-    if (ret != EOK) {
+    kerr = add_ticket_times_and_upn_to_response(kr);
+    if (kerr != 0) {
         DEBUG(1, ("add_ticket_times_and_upn_to_response failed.\n"));
     }
 
-    status = PAM_SUCCESS;
-    kerr = 0;
+    kerr = k5c_attach_ccname_msg(kr);
 
 done:
     krb5_free_cred_contents(kr->ctx, kr->creds);
@@ -1344,26 +1276,24 @@ done:
         krb5_cc_close(kr->ctx, ccache);
     }
 
-    *_status = status;
-    return kerr;
+    return map_krb5_error(kerr);
 }
 
-static errno_t create_empty_ccache(struct krb5_req *kr, int *_status)
+static errno_t create_empty_ccache(struct krb5_req *kr)
 {
-    int ret;
-    int pam_status = PAM_SUCCESS;
+    krb5_error_code kerr;
 
     DEBUG(SSSDBG_TRACE_LIBS, ("Creating empty ccache\n"));
 
-    ret = create_ccache(kr->uid, kr->gid, kr->ctx,
-                        kr->princ, kr->ccname, NULL);
-    if (ret != 0) {
-        KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, ret);
-        pam_status = PAM_SYSTEM_ERR;
+    kerr = create_ccache(kr->uid, kr->gid, kr->ctx,
+                         kr->princ, kr->ccname, NULL);
+    if (kerr != 0) {
+        KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr);
+    } else {
+        kerr = k5c_attach_ccname_msg(kr);
     }
 
-    *_status = pam_status;
-    return ret;
+    return map_krb5_error(kerr);
 }
 
 static errno_t unpack_authtok(TALLOC_CTX *mem_ctx, struct sss_auth_token *tok,
@@ -1898,7 +1828,6 @@ int main(int argc, const char *argv[])
     int opt;
     poptContext pc;
     int debug_fd = -1;
-    int status;
     errno_t ret;
 
     struct poptOption long_options[] = {
@@ -1972,30 +1901,32 @@ int main(int argc, const char *argv[])
         /* If we are offline, we need to create an empty ccache file */
         if (offline) {
             DEBUG(SSSDBG_TRACE_FUNC, ("Will perform offline auth\n"));
-            ret = create_empty_ccache(kr, &status);
+            ret = create_empty_ccache(kr);
         } else {
             DEBUG(SSSDBG_TRACE_FUNC, ("Will perform online auth\n"));
-            ret = tgt_req_child(kr, &status);
+            ret = tgt_req_child(kr);
         }
         break;
     case SSS_PAM_CHAUTHTOK:
-    case SSS_PAM_CHAUTHTOK_PRELIM:
         DEBUG(SSSDBG_TRACE_FUNC, ("Will perform password change\n"));
-        ret = changepw_child(kr, &status);
+        ret = changepw_child(kr, false);
+        break;
+    case SSS_PAM_CHAUTHTOK_PRELIM:
+        DEBUG(SSSDBG_TRACE_FUNC, ("Will perform password change checks\n"));
+        ret = changepw_child(kr, true);
         break;
     case SSS_PAM_ACCT_MGMT:
         DEBUG(SSSDBG_TRACE_FUNC, ("Will perform account management\n"));
-        ret = kuserok_child(kr, &status);
+        ret = kuserok_child(kr);
         break;
     case SSS_CMD_RENEW:
-        if (!offline) {
-            DEBUG(SSSDBG_TRACE_FUNC, ("Will perform ticket renewal\n"));
-            ret = renew_tgt_child(kr, &status);
-        } else {
+        if (offline) {
             DEBUG(SSSDBG_CRIT_FAILURE, ("Cannot renew TGT while offline\n"));
             ret = KRB5_KDC_UNREACH;
             goto done;
         }
+        DEBUG(SSSDBG_TRACE_FUNC, ("Will perform ticket renewal\n"));
+        ret = renew_tgt_child(kr);
         break;
     default:
         DEBUG(1, ("PAM command [%d] not supported.\n", kr->pd->cmd));
@@ -2003,7 +1934,7 @@ int main(int argc, const char *argv[])
         goto done;
     }
 
-    ret = k5c_send_data(kr, STDOUT_FILENO, ret, status);
+    ret = k5c_send_data(kr, STDOUT_FILENO, ret);
     if (ret != EOK) {
         DEBUG(SSSDBG_CRIT_FAILURE, ("Failed to send reply\n"));
     }
diff --git a/src/util/util_errors.c b/src/util/util_errors.c
index c196aae38a696df0333ed2de611202340066510e..1760c8d84e8c4c4e3906692cc9acbe166a4279fc 100644
--- a/src/util/util_errors.c
+++ b/src/util/util_errors.c
@@ -28,10 +28,15 @@ struct err_string error_to_str[] = {
     { "Invalid Error" },        /* ERR_INVALID */
     { "Internal Error" },       /* ERR_INTERNAL */
     { "Account Unknown" },      /* ERR_ACCOUNT_UNKNOWN */
+    { "Invalid credential type" },  /* ERR_INVALID_CRED_TYPE */
+    { "No credentials available" }, /* ERR_NO_CREDS */
+    { "Credentials are expired" }, /* ERR_CREDS_EXPIRED */
     { "No cached credentials available" }, /* ERR_NO_CACHED_CREDS */
     { "Cached credentials are expired" }, /* ERR_CACHED_CREDS_EXPIRED */
     { "Authentication Denied" }, /* ERR_AUTH_DENIED */
-    { "Authentication Failed" }, /* ERR_AUTH_DENIED */
+    { "Authentication Failed" }, /* ERR_AUTH_FAILED */
+    { "Password Change Failed" }, /* ERR_CHPASS_FAILED */
+    { "Network I/O Error" }, /* ERR_NETWORK_IO */
 };
 
 
diff --git a/src/util/util_errors.h b/src/util/util_errors.h
index 870d9d44bcb9a674e7208400ad649b7c761903de..9292c9959bed1d1772ca761c04f6db417c6286c1 100644
--- a/src/util/util_errors.h
+++ b/src/util/util_errors.h
@@ -50,10 +50,15 @@ enum sssd_errors {
     ERR_INVALID = ERR_BASE + 0,
     ERR_INTERNAL,
     ERR_ACCOUNT_UNKNOWN,
+    ERR_INVALID_CRED_TYPE,
+    ERR_NO_CREDS,
+    ERR_CREDS_EXPIRED,
     ERR_NO_CACHED_CREDS,
     ERR_CACHED_CREDS_EXPIRED,
     ERR_AUTH_DENIED,
     ERR_AUTH_FAILED,
+    ERR_CHPASS_FAILED,
+    ERR_NETWORK_IO,
     ERR_LAST            /* ALWAYS LAST */
 };
 
-- 
1.8.1.2

