summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/providers/krb5/krb5_auth.c192
-rw-r--r--src/providers/krb5/krb5_child.c177
-rw-r--r--src/sss_client/pam_sss.c14
-rw-r--r--src/sss_client/sss_cli.h6
4 files changed, 213 insertions, 176 deletions
diff --git a/src/providers/krb5/krb5_auth.c b/src/providers/krb5/krb5_auth.c
index c98a5f78..48173ba7 100644
--- a/src/providers/krb5/krb5_auth.c
+++ b/src/providers/krb5/krb5_auth.c
@@ -45,74 +45,6 @@
#define KRB5_CHILD SSSD_LIBEXEC_PATH"/krb5_child"
#endif
-static errno_t add_krb5_env(struct dp_option *opts, const char *ccname,
- struct pam_data *pd)
-{
- int ret;
- const char *dummy;
- char *env;
- TALLOC_CTX *tmp_ctx = NULL;
-
- tmp_ctx = talloc_new(NULL);
- if (tmp_ctx == NULL) {
- DEBUG(1, ("talloc_new failed.\n"));
- return ENOMEM;
- }
-
- if (ccname != NULL) {
- env = talloc_asprintf(tmp_ctx, "%s=%s",CCACHE_ENV_NAME, ccname);
- if (env == NULL) {
- DEBUG(1, ("talloc_asprintf failed.\n"));
- ret = ENOMEM;
- goto done;
- }
- ret = pam_add_response(pd, SSS_PAM_ENV_ITEM, strlen(env)+1,
- (uint8_t *) env);
- if (ret != EOK) {
- DEBUG(1, ("pam_add_response failed.\n"));
- goto done;
- }
- }
-
- dummy = dp_opt_get_cstring(opts, KRB5_REALM);
- if (dummy != NULL) {
- env = talloc_asprintf(tmp_ctx, "%s=%s", SSSD_KRB5_REALM, dummy);
- if (env == NULL) {
- DEBUG(1, ("talloc_asprintf failed.\n"));
- ret = ENOMEM;
- goto done;
- }
- ret = pam_add_response(pd, SSS_PAM_ENV_ITEM, strlen(env)+1,
- (uint8_t *) env);
- if (ret != EOK) {
- DEBUG(1, ("pam_add_response failed.\n"));
- goto done;
- }
- }
-
- dummy = dp_opt_get_cstring(opts, KRB5_KDC);
- if (dummy != NULL) {
- env = talloc_asprintf(tmp_ctx, "%s=%s", SSSD_KRB5_KDC, dummy);
- if (env == NULL) {
- DEBUG(1, ("talloc_asprintf failed.\n"));
- ret = ENOMEM;
- goto done;
- }
- ret = pam_add_response(pd, SSS_PAM_ENV_ITEM, strlen(env)+1,
- (uint8_t *) env);
- if (ret != EOK) {
- DEBUG(1, ("pam_add_response failed.\n"));
- goto done;
- }
- }
-
- ret = EOK;
-
-done:
- talloc_free(tmp_ctx);
- return ret;
-}
-
static errno_t check_if_ccache_file_is_used(uid_t uid, const char *ccname,
bool *result)
{
@@ -1035,10 +967,10 @@ static void krb5_child_done(struct tevent_req *subreq)
uint8_t *buf = NULL;
ssize_t len = -1;
ssize_t pref_len;
- int p;
- int32_t *msg_status;
- int32_t *msg_type;
- int32_t *msg_len;
+ size_t p;
+ int32_t msg_status;
+ int32_t msg_type;
+ int32_t msg_len;
ret = handle_child_recv(subreq, pd, &buf, &len);
talloc_zfree(kr->timeout_handler);
@@ -1055,48 +987,83 @@ static void krb5_child_done(struct tevent_req *subreq)
return;
}
- if ((size_t) len < 3*sizeof(int32_t)) {
+ /* A buffer with the following structure is expected.
+ * int32_t status of the request (required)
+ * message (zero or more)
+ *
+ * A message consists of:
+ * int32_t type of the message
+ * int32_t length of the following data
+ * uint8_t[len] data
+ */
+
+ if ((size_t) len < sizeof(int32_t)) {
DEBUG(1, ("message too short.\n"));
ret = EINVAL;
goto done;
}
p=0;
- msg_status = ((int32_t *)(buf+p));
- p += sizeof(int32_t);
+ SAFEALIGN_COPY_INT32(&msg_status, buf+p, &p);
- msg_type = ((int32_t *)(buf+p));
- p += sizeof(int32_t);
+ while (p < len) {
+ SAFEALIGN_COPY_INT32(&msg_type, buf+p, &p);
+ SAFEALIGN_COPY_INT32(&msg_len, buf+p, &p);
- msg_len = ((int32_t *)(buf+p));
- p += sizeof(int32_t);
+ DEBUG(9, ("child response [%d][%d][%d].\n", msg_status, msg_type,
+ msg_len));
- DEBUG(4, ("child response [%d][%d][%d].\n", *msg_status, *msg_type,
- *msg_len));
-
- if ((p + *msg_len) != len) {
- DEBUG(1, ("message format error [%d] != [%d].\n", p+*msg_len, len));
- goto done;
- }
+ if ((p + msg_len) > len) {
+ DEBUG(1, ("message format error [%d] > [%d].\n", p+msg_len, len));
+ ret = EINVAL;
+ goto done;
+ }
- if (*msg_status != PAM_SUCCESS && *msg_status != PAM_AUTHINFO_UNAVAIL &&
- *msg_status != PAM_AUTHTOK_LOCK_BUSY) {
- state->pam_status = *msg_status;
- state->dp_err = DP_ERR_OK;
+ /* We need to save the name of the credential cache file. To find it
+ * we check if the data part of a message starts with
+ * CCACHE_ENV_NAME"=". pref_len also counts the trailing '=' because
+ * sizeof() counts the trailing '\0' of a string. */
+ pref_len = sizeof(CCACHE_ENV_NAME);
+ if (msg_len > pref_len &&
+ strncmp((const char *) &buf[p], CCACHE_ENV_NAME"=", pref_len) == 0) {
+ kr->ccname = talloc_strndup(kr, (char *) &buf[p+pref_len],
+ msg_len-pref_len);
+ if (kr->ccname == NULL) {
+ DEBUG(1, ("talloc_strndup failed.\n"));
+ ret = ENOMEM;
+ goto done;
+ }
+ }
- ret = pam_add_response(pd, *msg_type, *msg_len, &buf[p]);
+ ret = pam_add_response(pd, msg_type, msg_len, &buf[p]);
if (ret != EOK) {
/* This is not a fatal error */
DEBUG(1, ("pam_add_response failed.\n"));
}
+ p += msg_len;
+
+ if ((p < len) && (p + 2*sizeof(int32_t) >= len)) {
+ DEBUG(1, ("The remainder of the message is too short.\n"));
+ ret = EINVAL;
+ goto done;
+ }
+ }
+ /* If the child request failed, but did not return an offline error code,
+ * return with the status */
+ if (msg_status != PAM_SUCCESS && msg_status != PAM_AUTHINFO_UNAVAIL &&
+ msg_status != PAM_AUTHTOK_LOCK_BUSY) {
+ state->pam_status = msg_status;
+ state->dp_err = DP_ERR_OK;
ret = EOK;
goto done;
} else {
- state->pam_status = *msg_status;
+ state->pam_status = msg_status;
}
- if (*msg_status == PAM_SUCCESS && pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM) {
+ /* If the child request was successful and we run the first pass of the
+ * change password request just return success. */
+ if (msg_status == PAM_SUCCESS && pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM) {
state->pam_status = PAM_SUCCESS;
state->dp_err = DP_ERR_OK;
ret = EOK;
@@ -1106,7 +1073,7 @@ static void krb5_child_done(struct tevent_req *subreq)
/* if using a dedicated kpasswd server.. */
if (kr->kpasswd_srv != NULL) {
/* ..which is unreachable by now.. */
- if (*msg_status == PAM_AUTHTOK_LOCK_BUSY) {
+ if (msg_status == PAM_AUTHTOK_LOCK_BUSY) {
fo_set_port_status(kr->kpasswd_srv, PORT_NOT_WORKING);
/* ..try to resolve next kpasswd server */
if (krb5_next_kpasswd(req) == NULL) {
@@ -1121,8 +1088,8 @@ static void krb5_child_done(struct tevent_req *subreq)
/* 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 (*msg_status == PAM_AUTHINFO_UNAVAIL ||
- (kr->kpasswd_srv == NULL && *msg_status == PAM_AUTHTOK_LOCK_BUSY)) {
+ if (msg_status == PAM_AUTHINFO_UNAVAIL ||
+ (kr->kpasswd_srv == NULL && msg_status == PAM_AUTHTOK_LOCK_BUSY)) {
if (kr->srv != NULL) {
fo_set_port_status(kr->srv, PORT_NOT_WORKING);
/* ..try to resolve next KDC */
@@ -1135,19 +1102,15 @@ static void krb5_child_done(struct tevent_req *subreq)
fo_set_port_status(kr->srv, PORT_WORKING);
}
- pref_len = strlen(CCACHE_ENV_NAME)+1;
- if (*msg_len > pref_len &&
- strncmp((const char *) &buf[p], CCACHE_ENV_NAME"=", pref_len) == 0) {
- kr->ccname = talloc_strndup(kr, (char *) &buf[p+pref_len],
- *msg_len-pref_len);
- if (kr->ccname == NULL) {
- DEBUG(1, ("talloc_strndup failed.\n"));
- ret = ENOMEM;
- goto done;
- }
- } else {
- DEBUG(1, ("Missing ccache name in child response [%.*s].\n", *msg_len,
- &buf[p]));
+ /* The following cases are left now:
+ * - offline (msg_status == PAM_AUTHINFO_UNAVAIL or
+ * msg_status == PAM_AUTHTOK_LOCK_BUSY)
+ * - successful authentication or password change
+ *
+ * For all these cases we expect that one of the messages for the
+ * received buffer contains the name of the credential cache file. */
+ if (kr->ccname == NULL) {
+ DEBUG(1, ("Missing ccache name in child response.\n"));
ret = EINVAL;
goto done;
}
@@ -1246,19 +1209,10 @@ static void krb5_save_ccname_done(struct tevent_req *req)
{
struct krb5_auth_state *state = tevent_req_data(req, struct krb5_auth_state);
struct krb5child_req *kr = state->kr;
- struct pam_data *pd = kr->pd;
- struct krb5_ctx *krb5_ctx = kr->krb5_ctx;
+ struct pam_data *pd = state->pd;
int ret;
char *password = NULL;
- if (pd->cmd == SSS_PAM_AUTHENTICATE || pd->cmd == SSS_PAM_CHAUTHTOK) {
- ret = add_krb5_env(krb5_ctx->opts, kr->ccname, pd);
- if (ret != EOK) {
- DEBUG(1, ("add_krb5_env failed.\n"));
- goto done;
- }
- }
-
if (kr->is_offline) {
DEBUG(4, ("Backend is marked offline, retry later!\n"));
state->pam_status = PAM_AUTHINFO_UNAVAIL;
diff --git a/src/providers/krb5/krb5_child.c b/src/providers/krb5/krb5_child.c
index 620e4d14..7249aeba 100644
--- a/src/providers/krb5/krb5_child.c
+++ b/src/providers/krb5/krb5_child.c
@@ -6,7 +6,7 @@
Authors:
Sumit Bose <sbose@redhat.com>
- Copyright (C) 2009 Red Hat
+ Copyright (C) 2009-2010 Red Hat
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -103,6 +103,36 @@ static const char *__krb5_error_msg;
sss_krb5_free_error_message(krb5_error_ctx, __krb5_error_msg); \
} while(0);
+
+static krb5_error_code sss_krb5_prompter(krb5_context context, void *data,
+ const char *name, const char *banner,
+ int num_prompts, krb5_prompt prompts[])
+{
+ int ret;
+ struct krb5_req *kr = talloc_get_type(data, struct krb5_req);
+
+ if (num_prompts != 0) {
+ DEBUG(1, ("Cannot handle password prompts.\n"));
+ return KRB5_LIBOS_CANTREADPWD;
+ }
+
+ if (banner == NULL || *banner == '\0') {
+ DEBUG(5, ("Prompter called with empty banner, nothing to do.\n"));
+ return EOK;
+ }
+
+ DEBUG(9, ("Prompter called with [%s].\n", banner));
+
+ ret = pam_add_response(kr->pd, SSS_PAM_TEXT_MSG, strlen(banner)+1,
+ (const uint8_t *) banner);
+ if (ret != EOK) {
+ DEBUG(1, ("pam_add_response failed.\n"));
+ }
+
+ return EOK;
+}
+
+
static krb5_error_code create_empty_cred(struct krb5_req *kr, krb5_creds **_cred)
{
krb5_error_code kerr;
@@ -247,22 +277,49 @@ done:
return kerr;
}
-static errno_t pack_response_packet(struct response *resp, int status, int type,
- size_t len, const uint8_t *data)
+static errno_t pack_response_packet(struct response *resp, int status,
+ struct pam_data *pd)
{
+ size_t size = 0;
size_t p = 0;
+ struct response_data *pdr;
+
+ /* A buffer with the following structure must be created:
+ * int32_t status of the request (required)
+ * message (zero or more)
+ *
+ * A message consists of:
+ * int32_t type of the message
+ * int32_t length of the following data
+ * uint8_t[len] data
+ */
- resp->buf = talloc_array(resp, uint8_t,
- 3*sizeof(int32_t) + len);
+ size = sizeof(int32_t);
+
+ pdr = pd->resp_list;
+ while (pdr != NULL) {
+ size += 2*sizeof(int32_t) + pdr->len;
+ pdr = pdr->next;
+ }
+
+
+ resp->buf = talloc_array(resp, uint8_t, size);
if (!resp->buf) {
DEBUG(1, ("Insufficient memory to create message.\n"));
return ENOMEM;
}
SAFEALIGN_SET_INT32(&resp->buf[p], status, &p);
- SAFEALIGN_SET_INT32(&resp->buf[p], type, &p);
- SAFEALIGN_SET_INT32(&resp->buf[p], len, &p);
- safealign_memcpy(&resp->buf[p], data, len, &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;
+ }
+
resp->size = p;
@@ -271,15 +328,12 @@ static errno_t pack_response_packet(struct response *resp, int status, int type,
static struct response *prepare_response_message(struct krb5_req *kr,
krb5_error_code kerr,
- char *user_error_message,
int pam_status)
{
char *msg = NULL;
const char *krb5_msg = NULL;
int ret;
struct response *resp;
- size_t user_resp_len;
- uint8_t *user_resp;
resp = talloc_zero(kr, struct response);
if (resp == NULL) {
@@ -289,9 +343,8 @@ static struct response *prepare_response_message(struct krb5_req *kr,
if (kerr == 0) {
if(kr->pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM) {
- ret = pack_response_packet(resp, PAM_SUCCESS, SSS_PAM_SYSTEM_INFO,
- strlen("success") + 1,
- (const uint8_t *) "success");
+ pam_status = PAM_SUCCESS;
+ ret = EOK;
} else {
if (kr->ccname == NULL) {
DEBUG(1, ("Error obtaining ccname.\n"));
@@ -304,44 +357,28 @@ static struct response *prepare_response_message(struct krb5_req *kr,
return NULL;
}
- ret = pack_response_packet(resp, PAM_SUCCESS, SSS_PAM_ENV_ITEM,
- strlen(msg) + 1, (uint8_t *) msg);
+ pam_status = PAM_SUCCESS;
+ ret = pam_add_response(kr->pd, SSS_PAM_ENV_ITEM, strlen(msg) + 1,
+ (uint8_t *) msg);
talloc_zfree(msg);
}
} else {
- if (user_error_message != NULL) {
- ret = pack_user_info_chpass_error(kr, user_error_message,
- &user_resp_len, &user_resp);
- if (ret != EOK) {
- DEBUG(1, ("pack_user_info_chpass_error failed.\n"));
- talloc_zfree(user_error_message);
- } else {
- ret = pack_response_packet(resp, pam_status, SSS_PAM_USER_INFO,
- user_resp_len, user_resp);
- if (ret != EOK) {
- DEBUG(1, ("pack_response_packet failed.\n"));
- talloc_zfree(user_error_message);
- }
- }
- }
-
- if (user_error_message == NULL) {
- 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 = pack_response_packet(resp, pam_status, SSS_PAM_SYSTEM_INFO,
- strlen(krb5_msg) + 1,
- (const uint8_t *) krb5_msg);
- sss_krb5_free_error_message(krb5_error_ctx, krb5_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"));
}
+ ret = pack_response_packet(resp, pam_status, kr->pd);
if (ret != EOK) {
DEBUG(1, ("pack_response_packet failed.\n"));
return NULL;
@@ -350,15 +387,14 @@ static struct response *prepare_response_message(struct krb5_req *kr,
return resp;
}
-static errno_t sendresponse(int fd, krb5_error_code kerr,
- char *user_error_message, int pam_status,
+static errno_t sendresponse(int fd, krb5_error_code kerr, int pam_status,
struct krb5_req *kr)
{
struct response *resp;
size_t written;
int ret;
- resp = prepare_response_message(kr, kerr, user_error_message, pam_status);
+ resp = prepare_response_message(kr, kerr, pam_status);
if (resp == NULL) {
DEBUG(1, ("prepare_response_message failed.\n"));
return ENOMEM;
@@ -481,8 +517,8 @@ static krb5_error_code get_and_save_tgt(struct krb5_req *kr,
int ret;
kerr = krb5_get_init_creds_password(kr->ctx, kr->creds, kr->princ,
- password, NULL, NULL, 0, NULL,
- kr->options);
+ password, sss_krb5_prompter, kr, 0,
+ NULL, kr->options);
if (kerr != 0) {
KRB5_DEBUG(1, kerr);
return kerr;
@@ -533,6 +569,9 @@ static errno_t changepw_child(int fd, struct krb5_req *kr)
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;
pass_str = talloc_strndup(kr, (const char *) kr->pd->authtok,
kr->pd->authtok_size);
@@ -542,8 +581,13 @@ static errno_t changepw_child(int fd, struct krb5_req *kr)
goto sendresponse;
}
+ if (kr->pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM) {
+ /* We do not need a password expiration warning here. */
+ prompter = NULL;
+ }
+
kerr = krb5_get_init_creds_password(kr->ctx, kr->creds, kr->princ,
- pass_str, NULL, NULL, 0,
+ pass_str, prompter, kr, 0,
kr->krb5_ctx->changepw_principle,
kr->options);
if (kerr != 0) {
@@ -612,6 +656,20 @@ static errno_t changepw_child(int fd, struct krb5_req *kr)
}
}
+ if (user_error_message != NULL) {
+ ret = pack_user_info_chpass_error(kr->pd, user_error_message,
+ &user_resp_len, &user_resp);
+ if (ret != EOK) {
+ DEBUG(1, ("pack_user_info_chpass_error failed.\n"));
+ } else {
+ ret = pam_add_response(kr->pd, SSS_PAM_USER_INFO, user_resp_len,
+ user_resp);
+ if (ret != EOK) {
+ DEBUG(1, ("pack_response_packet failed.\n"));
+ }
+ }
+ }
+
pam_status = PAM_AUTHTOK_ERR;
goto sendresponse;
}
@@ -631,7 +689,7 @@ static errno_t changepw_child(int fd, struct krb5_req *kr)
}
sendresponse:
- ret = sendresponse(fd, kerr, user_error_message, pam_status, kr);
+ ret = sendresponse(fd, kerr, pam_status, kr);
if (ret != EOK) {
DEBUG(1, ("sendresponse failed.\n"));
}
@@ -662,7 +720,7 @@ static errno_t tgt_req_child(int fd, struct krb5_req *kr)
So we validate the password by trying to get a changepw ticket. */
if (kerr == KRB5KDC_ERR_KEY_EXP) {
kerr = krb5_get_init_creds_password(kr->ctx, kr->creds, kr->princ,
- pass_str, NULL, NULL, 0,
+ pass_str, sss_krb5_prompter, kr, 0,
kr->krb5_ctx->changepw_principle,
kr->options);
krb5_free_cred_contents(kr->ctx, kr->creds);
@@ -693,7 +751,7 @@ static errno_t tgt_req_child(int fd, struct krb5_req *kr)
}
sendresponse:
- ret = sendresponse(fd, kerr, NULL, pam_status, kr);
+ ret = sendresponse(fd, kerr, pam_status, kr);
if (ret != EOK) {
DEBUG(1, ("sendresponse failed.\n"));
}
@@ -712,7 +770,7 @@ static errno_t create_empty_ccache(int fd, struct krb5_req *kr)
pam_status = PAM_SYSTEM_ERR;
}
- ret = sendresponse(fd, ret, NULL, pam_status, kr);
+ ret = sendresponse(fd, ret, pam_status, kr);
if (ret != EOK) {
DEBUG(1, ("sendresponse failed.\n"));
}
@@ -878,6 +936,15 @@ static int krb5_setup(struct krb5_req *kr, uint32_t offline)
goto failed;
}
+ /* A prompter is used to catch messages about when a password will
+ * expired. The library shall not use the prompter to ask for a new password
+ * but shall return KRB5KDC_ERR_KEY_EXP. */
+ krb5_get_init_creds_opt_set_change_password_prompt(kr->options, 0);
+ if (kerr != 0) {
+ KRB5_DEBUG(1, kerr);
+ goto failed;
+ }
+
/* TODO: set options, e.g.
* krb5_get_init_creds_opt_set_tkt_life
* krb5_get_init_creds_opt_set_renew_life
diff --git a/src/sss_client/pam_sss.c b/src/sss_client/pam_sss.c
index 1b98cf01..d2758004 100644
--- a/src/sss_client/pam_sss.c
+++ b/src/sss_client/pam_sss.c
@@ -894,7 +894,7 @@ static int eval_response(pam_handle_t *pamh, size_t buflen, uint8_t *buf,
switch(type) {
case SSS_PAM_SYSTEM_INFO:
if (buf[p + (len -1)] != '\0') {
- D(("user info does not end with \\0."));
+ D(("system info does not end with \\0."));
break;
}
logger(pamh, LOG_INFO, "system info: [%s]", &buf[p]);
@@ -946,6 +946,18 @@ static int eval_response(pam_handle_t *pamh, size_t buflen, uint8_t *buf,
D(("eval_user_info_response failed"));
}
break;
+ case SSS_PAM_TEXT_MSG:
+ if (buf[p + (len -1)] != '\0') {
+ D(("system info does not end with \\0."));
+ break;
+ }
+
+ ret = do_pam_conversation(pamh, PAM_TEXT_INFO, (char *) &buf[p],
+ NULL, NULL);
+ if (ret != PAM_SUCCESS) {
+ D(("do_pam_conversation failed."));
+ }
+ break;
default:
D(("Unknown response type [%d]", type));
}
diff --git a/src/sss_client/sss_cli.h b/src/sss_client/sss_cli.h
index f7e58fe9..8712a6f9 100644
--- a/src/sss_client/sss_cli.h
+++ b/src/sss_client/sss_cli.h
@@ -316,9 +316,13 @@ enum response_type {
* @param String, zero terminated, of the form
* name=value. See putenv(3) and pam_putenv(3) for
* details. */
- SSS_PAM_USER_INFO /**< A message which should be displayed to the user.
+ SSS_PAM_USER_INFO, /**< A message which should be displayed to the user.
* @param User info message, see #user_info_type
* for details. */
+ SSS_PAM_TEXT_MSG, /**< A plain text message which should be displayed to
+ * the user.This should only be used in the case where
+ * it is not possile to use SSS_PAM_USER_INFO.
+ * @param A zero terminated string. */
};
/**