2N/A/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2N/A/*
2N/A** set password functions added by Paul W. Nelson, Thursby Software Systems, Inc.
2N/A*/
2N/A#include <string.h>
2N/A
2N/A#include "k5-int.h"
2N/A#include "auth_con.h"
2N/A
2N/A
2N/Akrb5_error_code
2N/Akrb5int_mk_chpw_req(krb5_context context,
2N/A krb5_auth_context auth_context,
2N/A krb5_data *ap_req,
2N/A char *passwd,
2N/A krb5_data *packet)
2N/A{
2N/A krb5_error_code ret = 0;
2N/A krb5_data clearpw;
2N/A krb5_data cipherpw;
2N/A krb5_replay_data replay;
2N/A char *ptr;
2N/A
2N/A cipherpw.data = NULL;
2N/A
2N/A if ((ret = krb5_auth_con_setflags(context, auth_context,
2N/A KRB5_AUTH_CONTEXT_DO_SEQUENCE)))
2N/A goto cleanup;
2N/A
2N/A clearpw.length = strlen(passwd);
2N/A clearpw.data = passwd;
2N/A
2N/A if ((ret = krb5_mk_priv(context, auth_context,
2N/A &clearpw, &cipherpw, &replay)))
2N/A goto cleanup;
2N/A
2N/A packet->length = 6 + ap_req->length + cipherpw.length;
2N/A packet->data = (char *) malloc(packet->length);
2N/A if (packet->data == NULL) {
2N/A ret = ENOMEM;
2N/A goto cleanup;
2N/A }
2N/A ptr = packet->data;
2N/A
2N/A /* length */
2N/A
2N/A store_16_be(packet->length, ptr);
2N/A ptr += 2;
2N/A
2N/A /* version == 0x0001 big-endian */
2N/A
2N/A *ptr++ = 0;
2N/A *ptr++ = 1;
2N/A
2N/A /* ap_req length, big-endian */
2N/A
2N/A store_16_be(ap_req->length, ptr);
2N/A ptr += 2;
2N/A
2N/A /* ap-req data */
2N/A
2N/A memcpy(ptr, ap_req->data, ap_req->length);
2N/A ptr += ap_req->length;
2N/A
2N/A /* krb-priv of password */
2N/A
2N/A memcpy(ptr, cipherpw.data, cipherpw.length);
2N/A
2N/Acleanup:
2N/A if (cipherpw.data != NULL) /* allocated by krb5_mk_priv */
2N/A free(cipherpw.data);
2N/A
2N/A return(ret);
2N/A}
2N/A
2N/Akrb5_error_code
2N/Akrb5int_rd_chpw_rep(krb5_context context, krb5_auth_context auth_context,
2N/A krb5_data *packet, int *result_code, krb5_data *result_data)
2N/A{
2N/A char *ptr;
2N/A int plen, vno;
2N/A krb5_data ap_rep;
2N/A krb5_ap_rep_enc_part *ap_rep_enc;
2N/A krb5_error_code ret;
2N/A krb5_data cipherresult;
2N/A krb5_data clearresult;
2N/A krb5_error *krberror = NULL;
2N/A krb5_replay_data replay;
2N/A krb5_keyblock *tmp;
2N/A
2N/A if (packet->length < 4)
2N/A /* either this, or the server is printing bad messages,
2N/A or the caller passed in garbage */
2N/A return(KRB5KRB_AP_ERR_MODIFIED);
2N/A
2N/A ptr = packet->data;
2N/A
2N/A /* verify length */
2N/A
2N/A plen = (*ptr++ & 0xff);
2N/A plen = (plen<<8) | (*ptr++ & 0xff);
2N/A
2N/A if (plen != packet->length) {
2N/A /*
2N/A * MS KDCs *may* send back a KRB_ERROR. Although
2N/A * not 100% correct via RFC3244, it's something
2N/A * we can workaround here.
2N/A */
2N/A if (krb5_is_krb_error(packet)) {
2N/A
2N/A if ((ret = krb5_rd_error(context, packet, &krberror)))
2N/A return(ret);
2N/A
2N/A if (krberror->e_data.data == NULL)
2N/A ret = ERROR_TABLE_BASE_krb5 + (krb5_error_code) krberror->error;
2N/A else
2N/A ret = KRB5KRB_AP_ERR_MODIFIED;
2N/A krb5_free_error(context, krberror);
2N/A return(ret);
2N/A } else {
2N/A return(KRB5KRB_AP_ERR_MODIFIED);
2N/A }
2N/A }
2N/A
2N/A
2N/A /* verify version number */
2N/A
2N/A vno = (*ptr++ & 0xff);
2N/A vno = (vno<<8) | (*ptr++ & 0xff);
2N/A
2N/A if (vno != 1)
2N/A return(KRB5KDC_ERR_BAD_PVNO);
2N/A
2N/A /* read, check ap-rep length */
2N/A
2N/A ap_rep.length = (*ptr++ & 0xff);
2N/A ap_rep.length = (ap_rep.length<<8) | (*ptr++ & 0xff);
2N/A
2N/A if (ptr + ap_rep.length >= packet->data + packet->length)
2N/A return(KRB5KRB_AP_ERR_MODIFIED);
2N/A
2N/A if (ap_rep.length) {
2N/A /* verify ap_rep */
2N/A ap_rep.data = ptr;
2N/A ptr += ap_rep.length;
2N/A
2N/A /*
2N/A * Save send_subkey to later smash recv_subkey.
2N/A */
2N/A ret = krb5_auth_con_getsendsubkey(context, auth_context, &tmp);
2N/A if (ret)
2N/A return ret;
2N/A
2N/A ret = krb5_rd_rep(context, auth_context, &ap_rep, &ap_rep_enc);
2N/A if (ret) {
2N/A krb5_free_keyblock(context, tmp);
2N/A return(ret);
2N/A }
2N/A
2N/A krb5_free_ap_rep_enc_part(context, ap_rep_enc);
2N/A
2N/A /* extract and decrypt the result */
2N/A
2N/A cipherresult.data = ptr;
2N/A cipherresult.length = (packet->data + packet->length) - ptr;
2N/A
2N/A /*
2N/A * Smash recv_subkey to be send_subkey, per spec.
2N/A */
2N/A ret = krb5_auth_con_setrecvsubkey(context, auth_context, tmp);
2N/A krb5_free_keyblock(context, tmp);
2N/A if (ret)
2N/A return ret;
2N/A
2N/A ret = krb5_rd_priv(context, auth_context, &cipherresult, &clearresult,
2N/A &replay);
2N/A
2N/A if (ret)
2N/A return(ret);
2N/A } else {
2N/A cipherresult.data = ptr;
2N/A cipherresult.length = (packet->data + packet->length) - ptr;
2N/A
2N/A if ((ret = krb5_rd_error(context, &cipherresult, &krberror)))
2N/A return(ret);
2N/A
2N/A clearresult = krberror->e_data;
2N/A }
2N/A
2N/A if (clearresult.length < 2) {
2N/A ret = KRB5KRB_AP_ERR_MODIFIED;
2N/A goto cleanup;
2N/A }
2N/A
2N/A ptr = clearresult.data;
2N/A
2N/A *result_code = (*ptr++ & 0xff);
2N/A *result_code = (*result_code<<8) | (*ptr++ & 0xff);
2N/A
2N/A if ((*result_code < KRB5_KPASSWD_SUCCESS) ||
2N/A (*result_code > KRB5_KPASSWD_INITIAL_FLAG_NEEDED)) {
2N/A ret = KRB5KRB_AP_ERR_MODIFIED;
2N/A goto cleanup;
2N/A }
2N/A
2N/A /* all success replies should be authenticated/encrypted */
2N/A
2N/A if ((ap_rep.length == 0) && (*result_code == KRB5_KPASSWD_SUCCESS)) {
2N/A ret = KRB5KRB_AP_ERR_MODIFIED;
2N/A goto cleanup;
2N/A }
2N/A
2N/A result_data->length = (clearresult.data + clearresult.length) - ptr;
2N/A
2N/A if (result_data->length) {
2N/A result_data->data = (char *) malloc(result_data->length);
2N/A if (result_data->data == NULL) {
2N/A ret = ENOMEM;
2N/A goto cleanup;
2N/A }
2N/A memcpy(result_data->data, ptr, result_data->length);
2N/A } else {
2N/A result_data->data = NULL;
2N/A }
2N/A
2N/A ret = 0;
2N/A
2N/Acleanup:
2N/A if (ap_rep.length) {
2N/A free(clearresult.data);
2N/A } else {
2N/A krb5_free_error(context, krberror);
2N/A }
2N/A
2N/A return(ret);
2N/A}
2N/A
2N/Akrb5_error_code KRB5_CALLCONV
2N/Akrb5_chpw_result_code_string(krb5_context context, int result_code,
2N/A char **code_string)
2N/A{
2N/A switch (result_code) {
2N/A case KRB5_KPASSWD_MALFORMED:
2N/A *code_string = "Malformed request error";
2N/A break;
2N/A case KRB5_KPASSWD_HARDERROR:
2N/A *code_string = "Server error";
2N/A break;
2N/A case KRB5_KPASSWD_AUTHERROR:
2N/A *code_string = "Authentication error";
2N/A break;
2N/A case KRB5_KPASSWD_SOFTERROR:
2N/A *code_string = "Password change rejected";
2N/A break;
2N/A default:
2N/A *code_string = "Password change failed";
2N/A break;
2N/A }
2N/A
2N/A return(0);
2N/A}
2N/A
2N/Akrb5_error_code
2N/Akrb5int_mk_setpw_req(krb5_context context,
2N/A krb5_auth_context auth_context,
2N/A krb5_data *ap_req,
2N/A krb5_principal targprinc,
2N/A char *passwd,
2N/A krb5_data *packet)
2N/A{
2N/A krb5_error_code ret;
2N/A krb5_data cipherpw;
2N/A krb5_data *encoded_setpw;
2N/A struct krb5_setpw_req req;
2N/A
2N/A char *ptr;
2N/A
2N/A cipherpw.data = NULL;
2N/A cipherpw.length = 0;
2N/A
2N/A if ((ret = krb5_auth_con_setflags(context, auth_context,
2N/A KRB5_AUTH_CONTEXT_DO_SEQUENCE)))
2N/A return(ret);
2N/A
2N/A req.target = targprinc;
2N/A req.password.data = passwd;
2N/A req.password.length = strlen(passwd);
2N/A ret = encode_krb5_setpw_req(&req, &encoded_setpw);
2N/A if (ret) {
2N/A return ret;
2N/A }
2N/A
2N/A if ((ret = krb5_mk_priv(context, auth_context, encoded_setpw, &cipherpw, NULL)) != 0) {
2N/A krb5_free_data(context, encoded_setpw);
2N/A return(ret);
2N/A }
2N/A krb5_free_data(context, encoded_setpw);
2N/A
2N/A
2N/A packet->length = 6 + ap_req->length + cipherpw.length;
2N/A packet->data = (char *) malloc(packet->length);
2N/A if (packet->data == NULL) {
2N/A ret = ENOMEM;
2N/A goto cleanup;
2N/A }
2N/A ptr = packet->data;
2N/A /*
2N/A ** build the packet -
2N/A */
2N/A /* put in the length */
2N/A store_16_be(packet->length, ptr);
2N/A ptr += 2;
2N/A /* put in the version */
2N/A *ptr++ = (char)0xff;
2N/A *ptr++ = (char)0x80;
2N/A /* the ap_req length is big endian */
2N/A store_16_be(ap_req->length, ptr);
2N/A ptr += 2;
2N/A /* put in the request data */
2N/A memcpy(ptr, ap_req->data, ap_req->length);
2N/A ptr += ap_req->length;
2N/A /*
2N/A ** put in the "private" password data -
2N/A */
2N/A memcpy(ptr, cipherpw.data, cipherpw.length);
2N/A ret = 0;
2N/Acleanup:
2N/A if (cipherpw.data)
2N/A krb5_free_data_contents(context, &cipherpw);
2N/A if ((ret != 0) && packet->data) {
2N/A free(packet->data);
2N/A packet->data = NULL;
2N/A }
2N/A return ret;
2N/A}
2N/A
2N/Akrb5_error_code
2N/Akrb5int_rd_setpw_rep(krb5_context context, krb5_auth_context auth_context,
2N/A krb5_data *packet,
2N/A int *result_code, krb5_data *result_data)
2N/A{
2N/A char *ptr;
2N/A unsigned int message_length, version_number;
2N/A krb5_data ap_rep;
2N/A krb5_ap_rep_enc_part *ap_rep_enc;
2N/A krb5_error_code ret;
2N/A krb5_data cipherresult;
2N/A krb5_data clearresult;
2N/A krb5_keyblock *tmpkey;
2N/A /*
2N/A ** validate the packet length -
2N/A */
2N/A if (packet->length < 4)
2N/A return(KRB5KRB_AP_ERR_MODIFIED);
2N/A
2N/A ptr = packet->data;
2N/A
2N/A /*
2N/A ** see if it is an error
2N/A */
2N/A if (krb5_is_krb_error(packet)) {
2N/A krb5_error *krberror;
2N/A if ((ret = krb5_rd_error(context, packet, &krberror)))
2N/A return(ret);
2N/A if (krberror->e_data.data == NULL) {
2N/A ret = ERROR_TABLE_BASE_krb5 + (krb5_error_code) krberror->error;
2N/A krb5_free_error(context, krberror);
2N/A return (ret);
2N/A }
2N/A clearresult = krberror->e_data;
2N/A krberror->e_data.data = NULL; /*So we can free it later*/
2N/A krberror->e_data.length = 0;
2N/A krb5_free_error(context, krberror);
2N/A ap_rep.length = 0;
2N/A
2N/A } else { /* Not an error*/
2N/A
2N/A /*
2N/A ** validate the message length -
2N/A ** length is big endian
2N/A */
2N/A message_length = (((ptr[0] << 8)&0xff) | (ptr[1]&0xff));
2N/A ptr += 2;
2N/A /*
2N/A ** make sure the message length and packet length agree -
2N/A */
2N/A if (message_length != packet->length)
2N/A return(KRB5KRB_AP_ERR_MODIFIED);
2N/A /*
2N/A ** get the version number -
2N/A */
2N/A version_number = (((ptr[0] << 8)&0xff) | (ptr[1]&0xff));
2N/A ptr += 2;
2N/A /*
2N/A ** make sure we support the version returned -
2N/A */
2N/A /*
2N/A ** set password version is 0xff80, change password version is 1
2N/A */
2N/A if (version_number != 1 && version_number != 0xff80)
2N/A return(KRB5KDC_ERR_BAD_PVNO);
2N/A /*
2N/A ** now fill in ap_rep with the reply -
2N/A */
2N/A /*
2N/A ** get the reply length -
2N/A */
2N/A ap_rep.length = (((ptr[0] << 8)&0xff) | (ptr[1]&0xff));
2N/A ptr += 2;
2N/A /*
2N/A ** validate ap_rep length agrees with the packet length -
2N/A */
2N/A if (ptr + ap_rep.length >= packet->data + packet->length)
2N/A return(KRB5KRB_AP_ERR_MODIFIED);
2N/A /*
2N/A ** if data was returned, set the ap_rep ptr -
2N/A */
2N/A if (ap_rep.length) {
2N/A ap_rep.data = ptr;
2N/A ptr += ap_rep.length;
2N/A
2N/A /*
2N/A * Save send_subkey to later smash recv_subkey.
2N/A */
2N/A ret = krb5_auth_con_getsendsubkey(context, auth_context, &tmpkey);
2N/A if (ret)
2N/A return ret;
2N/A
2N/A ret = krb5_rd_rep(context, auth_context, &ap_rep, &ap_rep_enc);
2N/A if (ret) {
2N/A krb5_free_keyblock(context, tmpkey);
2N/A return(ret);
2N/A }
2N/A
2N/A krb5_free_ap_rep_enc_part(context, ap_rep_enc);
2N/A /*
2N/A ** now decrypt the result -
2N/A */
2N/A cipherresult.data = ptr;
2N/A cipherresult.length = (packet->data + packet->length) - ptr;
2N/A
2N/A /*
2N/A * Smash recv_subkey to be send_subkey, per spec.
2N/A */
2N/A ret = krb5_auth_con_setrecvsubkey(context, auth_context, tmpkey);
2N/A krb5_free_keyblock(context, tmpkey);
2N/A if (ret)
2N/A return ret;
2N/A
2N/A ret = krb5_rd_priv(context, auth_context, &cipherresult, &clearresult,
2N/A NULL);
2N/A if (ret)
2N/A return(ret);
2N/A } /*We got an ap_rep*/
2N/A else
2N/A return (KRB5KRB_AP_ERR_MODIFIED);
2N/A } /*Response instead of error*/
2N/A
2N/A /*
2N/A ** validate the cleartext length
2N/A */
2N/A if (clearresult.length < 2) {
2N/A ret = KRB5KRB_AP_ERR_MODIFIED;
2N/A goto cleanup;
2N/A }
2N/A /*
2N/A ** now decode the result -
2N/A */
2N/A ptr = clearresult.data;
2N/A
2N/A *result_code = (((ptr[0] << 8)&0xff) | (ptr[1]&0xff));
2N/A ptr += 2;
2N/A
2N/A /*
2N/A ** result code 5 is access denied
2N/A */
2N/A if ((*result_code < KRB5_KPASSWD_SUCCESS) || (*result_code > 5)) {
2N/A ret = KRB5KRB_AP_ERR_MODIFIED;
2N/A goto cleanup;
2N/A }
2N/A /*
2N/A ** all success replies should be authenticated/encrypted
2N/A */
2N/A if ((ap_rep.length == 0) && (*result_code == KRB5_KPASSWD_SUCCESS)) {
2N/A ret = KRB5KRB_AP_ERR_MODIFIED;
2N/A goto cleanup;
2N/A }
2N/A
2N/A if (result_data) {
2N/A result_data->length = (clearresult.data + clearresult.length) - ptr;
2N/A
2N/A if (result_data->length) {
2N/A result_data->data = (char *) malloc(result_data->length);
2N/A if (result_data->data)
2N/A memcpy(result_data->data, ptr, result_data->length);
2N/A } else
2N/A result_data->data = NULL;
2N/A }
2N/A ret = 0;
2N/A
2N/Acleanup:
2N/A krb5_free_data_contents(context, &clearresult);
2N/A return(ret);
2N/A}
2N/A
2N/Akrb5_error_code
2N/Akrb5int_setpw_result_code_string(krb5_context context, int result_code,
2N/A const char **code_string)
2N/A{
2N/A switch (result_code) {
2N/A case KRB5_KPASSWD_MALFORMED:
2N/A *code_string = "Malformed request error";
2N/A break;
2N/A case KRB5_KPASSWD_HARDERROR:
2N/A *code_string = "Server error";
2N/A break;
2N/A case KRB5_KPASSWD_AUTHERROR:
2N/A *code_string = "Authentication error";
2N/A break;
2N/A case KRB5_KPASSWD_SOFTERROR:
2N/A *code_string = "Password change rejected";
2N/A break;
2N/A case 5: /* access denied */
2N/A *code_string = "Access denied";
2N/A break;
2N/A case 6: /* bad version */
2N/A *code_string = "Wrong protocol version";
2N/A break;
2N/A case 7: /* initial flag is needed */
2N/A *code_string = "Initial password required";
2N/A break;
2N/A case 0:
2N/A *code_string = "Success";
2N/A break;
2N/A default:
2N/A *code_string = "Password change failed";
2N/A break;
2N/A }
2N/A
2N/A return(0);
2N/A}