Path: blob/main/crypto/heimdal/lib/krb5/changepw.c
34878 views
/*1* Copyright (c) 1997 - 2005 Kungliga Tekniska Högskolan2* (Royal Institute of Technology, Stockholm, Sweden).3* All rights reserved.4*5* Redistribution and use in source and binary forms, with or without6* modification, are permitted provided that the following conditions7* are met:8*9* 1. Redistributions of source code must retain the above copyright10* notice, this list of conditions and the following disclaimer.11*12* 2. Redistributions in binary form must reproduce the above copyright13* notice, this list of conditions and the following disclaimer in the14* documentation and/or other materials provided with the distribution.15*16* 3. Neither the name of the Institute nor the names of its contributors17* may be used to endorse or promote products derived from this software18* without specific prior written permission.19*20* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND21* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE22* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE23* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE24* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL25* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS26* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)27* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT28* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY29* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF30* SUCH DAMAGE.31*/3233#include "krb5_locl.h"3435#undef __attribute__36#define __attribute__(X)373839static void40str2data (krb5_data *d,41const char *fmt,42...) __attribute__ ((format (printf, 2, 3)));4344static void45str2data (krb5_data *d,46const char *fmt,47...)48{49va_list args;50char *str;5152va_start(args, fmt);53d->length = vasprintf (&str, fmt, args);54va_end(args);55d->data = str;56}5758/*59* Change password protocol defined by60* draft-ietf-cat-kerb-chg-password-02.txt61*62* Share the response part of the protocol with MS set password63* (RFC3244)64*/6566static krb5_error_code67chgpw_send_request (krb5_context context,68krb5_auth_context *auth_context,69krb5_creds *creds,70krb5_principal targprinc,71int is_stream,72rk_socket_t sock,73const char *passwd,74const char *host)75{76krb5_error_code ret;77krb5_data ap_req_data;78krb5_data krb_priv_data;79krb5_data passwd_data;80size_t len;81u_char header[6];82struct iovec iov[3];83struct msghdr msghdr;8485if (is_stream)86return KRB5_KPASSWD_MALFORMED;8788if (targprinc &&89krb5_principal_compare(context, creds->client, targprinc) != TRUE)90return KRB5_KPASSWD_MALFORMED;9192krb5_data_zero (&ap_req_data);9394ret = krb5_mk_req_extended (context,95auth_context,96AP_OPTS_MUTUAL_REQUIRED | AP_OPTS_USE_SUBKEY,97NULL, /* in_data */98creds,99&ap_req_data);100if (ret)101return ret;102103passwd_data.data = rk_UNCONST(passwd);104passwd_data.length = strlen(passwd);105106krb5_data_zero (&krb_priv_data);107108ret = krb5_mk_priv (context,109*auth_context,110&passwd_data,111&krb_priv_data,112NULL);113if (ret)114goto out2;115116len = 6 + ap_req_data.length + krb_priv_data.length;117header[0] = (len >> 8) & 0xFF;118header[1] = (len >> 0) & 0xFF;119header[2] = 0;120header[3] = 1;121header[4] = (ap_req_data.length >> 8) & 0xFF;122header[5] = (ap_req_data.length >> 0) & 0xFF;123124memset(&msghdr, 0, sizeof(msghdr));125msghdr.msg_name = NULL;126msghdr.msg_namelen = 0;127msghdr.msg_iov = iov;128msghdr.msg_iovlen = sizeof(iov)/sizeof(*iov);129#if 0130msghdr.msg_control = NULL;131msghdr.msg_controllen = 0;132#endif133134iov[0].iov_base = (void*)header;135iov[0].iov_len = 6;136iov[1].iov_base = ap_req_data.data;137iov[1].iov_len = ap_req_data.length;138iov[2].iov_base = krb_priv_data.data;139iov[2].iov_len = krb_priv_data.length;140141if (rk_IS_SOCKET_ERROR( sendmsg (sock, &msghdr, 0) )) {142ret = rk_SOCK_ERRNO;143krb5_set_error_message(context, ret, "sendmsg %s: %s",144host, strerror(ret));145}146147krb5_data_free (&krb_priv_data);148out2:149krb5_data_free (&ap_req_data);150return ret;151}152153/*154* Set password protocol as defined by RFC3244 --155* Microsoft Windows 2000 Kerberos Change Password and Set Password Protocols156*/157158static krb5_error_code159setpw_send_request (krb5_context context,160krb5_auth_context *auth_context,161krb5_creds *creds,162krb5_principal targprinc,163int is_stream,164rk_socket_t sock,165const char *passwd,166const char *host)167{168krb5_error_code ret;169krb5_data ap_req_data;170krb5_data krb_priv_data;171krb5_data pwd_data;172ChangePasswdDataMS chpw;173size_t len = 0;174u_char header[4 + 6];175u_char *p;176struct iovec iov[3];177struct msghdr msghdr;178179krb5_data_zero (&ap_req_data);180181ret = krb5_mk_req_extended (context,182auth_context,183AP_OPTS_MUTUAL_REQUIRED | AP_OPTS_USE_SUBKEY,184NULL, /* in_data */185creds,186&ap_req_data);187if (ret)188return ret;189190chpw.newpasswd.length = strlen(passwd);191chpw.newpasswd.data = rk_UNCONST(passwd);192if (targprinc) {193chpw.targname = &targprinc->name;194chpw.targrealm = &targprinc->realm;195} else {196chpw.targname = NULL;197chpw.targrealm = NULL;198}199200ASN1_MALLOC_ENCODE(ChangePasswdDataMS, pwd_data.data, pwd_data.length,201&chpw, &len, ret);202if (ret) {203krb5_data_free (&ap_req_data);204return ret;205}206207if(pwd_data.length != len)208krb5_abortx(context, "internal error in ASN.1 encoder");209210ret = krb5_mk_priv (context,211*auth_context,212&pwd_data,213&krb_priv_data,214NULL);215if (ret)216goto out2;217218len = 6 + ap_req_data.length + krb_priv_data.length;219p = header;220if (is_stream) {221_krb5_put_int(p, len, 4);222p += 4;223}224*p++ = (len >> 8) & 0xFF;225*p++ = (len >> 0) & 0xFF;226*p++ = 0xff;227*p++ = 0x80;228*p++ = (ap_req_data.length >> 8) & 0xFF;229*p = (ap_req_data.length >> 0) & 0xFF;230231memset(&msghdr, 0, sizeof(msghdr));232msghdr.msg_name = NULL;233msghdr.msg_namelen = 0;234msghdr.msg_iov = iov;235msghdr.msg_iovlen = sizeof(iov)/sizeof(*iov);236#if 0237msghdr.msg_control = NULL;238msghdr.msg_controllen = 0;239#endif240241iov[0].iov_base = (void*)header;242if (is_stream)243iov[0].iov_len = 10;244else245iov[0].iov_len = 6;246iov[1].iov_base = ap_req_data.data;247iov[1].iov_len = ap_req_data.length;248iov[2].iov_base = krb_priv_data.data;249iov[2].iov_len = krb_priv_data.length;250251if (rk_IS_SOCKET_ERROR( sendmsg (sock, &msghdr, 0) )) {252ret = rk_SOCK_ERRNO;253krb5_set_error_message(context, ret, "sendmsg %s: %s",254host, strerror(ret));255}256257krb5_data_free (&krb_priv_data);258out2:259krb5_data_free (&ap_req_data);260krb5_data_free (&pwd_data);261return ret;262}263264static krb5_error_code265process_reply (krb5_context context,266krb5_auth_context auth_context,267int is_stream,268rk_socket_t sock,269int *result_code,270krb5_data *result_code_string,271krb5_data *result_string,272const char *host)273{274krb5_error_code ret;275u_char reply[1024 * 3];276size_t len;277uint16_t pkt_len, pkt_ver;278krb5_data ap_rep_data;279int save_errno;280281len = 0;282if (is_stream) {283while (len < sizeof(reply)) {284unsigned long size;285286ret = recvfrom (sock, reply + len, sizeof(reply) - len,2870, NULL, NULL);288if (rk_IS_SOCKET_ERROR(ret)) {289save_errno = rk_SOCK_ERRNO;290krb5_set_error_message(context, save_errno,291"recvfrom %s: %s",292host, strerror(save_errno));293return save_errno;294} else if (ret == 0) {295krb5_set_error_message(context, 1,"recvfrom timeout %s", host);296return 1;297}298len += ret;299if (len < 4)300continue;301_krb5_get_int(reply, &size, 4);302if (size + 4 < len)303continue;304memmove(reply, reply + 4, size);305len = size;306break;307}308if (len == sizeof(reply)) {309krb5_set_error_message(context, ENOMEM,310N_("Message too large from %s", "host"),311host);312return ENOMEM;313}314} else {315ret = recvfrom (sock, reply, sizeof(reply), 0, NULL, NULL);316if (rk_IS_SOCKET_ERROR(ret)) {317save_errno = rk_SOCK_ERRNO;318krb5_set_error_message(context, save_errno,319"recvfrom %s: %s",320host, strerror(save_errno));321return save_errno;322}323len = ret;324}325326if (len < 6) {327str2data (result_string, "server %s sent to too short message "328"(%zu bytes)", host, len);329*result_code = KRB5_KPASSWD_MALFORMED;330return 0;331}332333pkt_len = (reply[0] << 8) | (reply[1]);334pkt_ver = (reply[2] << 8) | (reply[3]);335336if ((pkt_len != len) || (reply[1] == 0x7e || reply[1] == 0x5e)) {337KRB_ERROR error;338size_t size;339u_char *p;340341memset(&error, 0, sizeof(error));342343ret = decode_KRB_ERROR(reply, len, &error, &size);344if (ret)345return ret;346347if (error.e_data->length < 2) {348str2data(result_string, "server %s sent too short "349"e_data to print anything usable", host);350free_KRB_ERROR(&error);351*result_code = KRB5_KPASSWD_MALFORMED;352return 0;353}354355p = error.e_data->data;356*result_code = (p[0] << 8) | p[1];357if (error.e_data->length == 2)358str2data(result_string, "server only sent error code");359else360krb5_data_copy (result_string,361p + 2,362error.e_data->length - 2);363free_KRB_ERROR(&error);364return 0;365}366367if (pkt_len != len) {368str2data (result_string, "client: wrong len in reply");369*result_code = KRB5_KPASSWD_MALFORMED;370return 0;371}372if (pkt_ver != KRB5_KPASSWD_VERS_CHANGEPW) {373str2data (result_string,374"client: wrong version number (%d)", pkt_ver);375*result_code = KRB5_KPASSWD_MALFORMED;376return 0;377}378379ap_rep_data.data = reply + 6;380ap_rep_data.length = (reply[4] << 8) | (reply[5]);381382if (reply + len < (u_char *)ap_rep_data.data + ap_rep_data.length) {383str2data (result_string, "client: wrong AP len in reply");384*result_code = KRB5_KPASSWD_MALFORMED;385return 0;386}387388if (ap_rep_data.length) {389krb5_ap_rep_enc_part *ap_rep;390krb5_data priv_data;391u_char *p;392393priv_data.data = (u_char*)ap_rep_data.data + ap_rep_data.length;394priv_data.length = len - ap_rep_data.length - 6;395396ret = krb5_rd_rep (context,397auth_context,398&ap_rep_data,399&ap_rep);400if (ret)401return ret;402403krb5_free_ap_rep_enc_part (context, ap_rep);404405ret = krb5_rd_priv (context,406auth_context,407&priv_data,408result_code_string,409NULL);410if (ret) {411krb5_data_free (result_code_string);412return ret;413}414415if (result_code_string->length < 2) {416*result_code = KRB5_KPASSWD_MALFORMED;417str2data (result_string,418"client: bad length in result");419return 0;420}421422p = result_code_string->data;423424*result_code = (p[0] << 8) | p[1];425krb5_data_copy (result_string,426(unsigned char*)result_code_string->data + 2,427result_code_string->length - 2);428return 0;429} else {430KRB_ERROR error;431size_t size;432u_char *p;433434ret = decode_KRB_ERROR(reply + 6, len - 6, &error, &size);435if (ret) {436return ret;437}438if (error.e_data->length < 2) {439krb5_warnx (context, "too short e_data to print anything usable");440return 1; /* XXX */441}442443p = error.e_data->data;444*result_code = (p[0] << 8) | p[1];445krb5_data_copy (result_string,446p + 2,447error.e_data->length - 2);448return 0;449}450}451452453/*454* change the password using the credentials in `creds' (for the455* principal indicated in them) to `newpw', storing the result of456* the operation in `result_*' and an error code or 0.457*/458459typedef krb5_error_code (*kpwd_send_request) (krb5_context,460krb5_auth_context *,461krb5_creds *,462krb5_principal,463int,464rk_socket_t,465const char *,466const char *);467typedef krb5_error_code (*kpwd_process_reply) (krb5_context,468krb5_auth_context,469int,470rk_socket_t,471int *,472krb5_data *,473krb5_data *,474const char *);475476static struct kpwd_proc {477const char *name;478int flags;479#define SUPPORT_TCP 1480#define SUPPORT_UDP 2481kpwd_send_request send_req;482kpwd_process_reply process_rep;483} procs[] = {484{485"MS set password",486SUPPORT_TCP|SUPPORT_UDP,487setpw_send_request,488process_reply489},490{491"change password",492SUPPORT_UDP,493chgpw_send_request,494process_reply495},496{ NULL, 0, NULL, NULL }497};498499/*500*501*/502503static krb5_error_code504change_password_loop (krb5_context context,505krb5_creds *creds,506krb5_principal targprinc,507const char *newpw,508int *result_code,509krb5_data *result_code_string,510krb5_data *result_string,511struct kpwd_proc *proc)512{513krb5_error_code ret;514krb5_auth_context auth_context = NULL;515krb5_krbhst_handle handle = NULL;516krb5_krbhst_info *hi;517rk_socket_t sock;518unsigned int i;519int done = 0;520krb5_realm realm;521522if (targprinc)523realm = targprinc->realm;524else525realm = creds->client->realm;526527ret = krb5_auth_con_init (context, &auth_context);528if (ret)529return ret;530531krb5_auth_con_setflags (context, auth_context,532KRB5_AUTH_CONTEXT_DO_SEQUENCE);533534ret = krb5_krbhst_init (context, realm, KRB5_KRBHST_CHANGEPW, &handle);535if (ret)536goto out;537538while (!done && (ret = krb5_krbhst_next(context, handle, &hi)) == 0) {539struct addrinfo *ai, *a;540int is_stream;541542switch (hi->proto) {543case KRB5_KRBHST_UDP:544if ((proc->flags & SUPPORT_UDP) == 0)545continue;546is_stream = 0;547break;548case KRB5_KRBHST_TCP:549if ((proc->flags & SUPPORT_TCP) == 0)550continue;551is_stream = 1;552break;553default:554continue;555}556557ret = krb5_krbhst_get_addrinfo(context, hi, &ai);558if (ret)559continue;560561for (a = ai; !done && a != NULL; a = a->ai_next) {562int replied = 0;563564sock = socket (a->ai_family, a->ai_socktype | SOCK_CLOEXEC, a->ai_protocol);565if (rk_IS_BAD_SOCKET(sock))566continue;567rk_cloexec(sock);568569ret = connect(sock, a->ai_addr, a->ai_addrlen);570if (rk_IS_SOCKET_ERROR(ret)) {571rk_closesocket (sock);572goto out;573}574575ret = krb5_auth_con_genaddrs (context, auth_context, sock,576KRB5_AUTH_CONTEXT_GENERATE_LOCAL_ADDR);577if (ret) {578rk_closesocket (sock);579goto out;580}581582for (i = 0; !done && i < 5; ++i) {583fd_set fdset;584struct timeval tv;585586if (!replied) {587replied = 0;588589ret = (*proc->send_req) (context,590&auth_context,591creds,592targprinc,593is_stream,594sock,595newpw,596hi->hostname);597if (ret) {598rk_closesocket(sock);599goto out;600}601}602603#ifndef NO_LIMIT_FD_SETSIZE604if (sock >= FD_SETSIZE) {605ret = ERANGE;606krb5_set_error_message(context, ret,607"fd %d too large", sock);608rk_closesocket (sock);609goto out;610}611#endif612613FD_ZERO(&fdset);614FD_SET(sock, &fdset);615tv.tv_usec = 0;616tv.tv_sec = 1 + (1 << i);617618ret = select (sock + 1, &fdset, NULL, NULL, &tv);619if (rk_IS_SOCKET_ERROR(ret) && rk_SOCK_ERRNO != EINTR) {620rk_closesocket(sock);621goto out;622}623if (ret == 1) {624ret = (*proc->process_rep) (context,625auth_context,626is_stream,627sock,628result_code,629result_code_string,630result_string,631hi->hostname);632if (ret == 0)633done = 1;634else if (i > 0 && ret == KRB5KRB_AP_ERR_MUT_FAIL)635replied = 1;636} else {637ret = KRB5_KDC_UNREACH;638}639}640rk_closesocket (sock);641}642}643644out:645krb5_krbhst_free (context, handle);646krb5_auth_con_free (context, auth_context);647648if (ret == KRB5_KDC_UNREACH) {649krb5_set_error_message(context,650ret,651N_("Unable to reach any changepw server "652" in realm %s", "realm"), realm);653*result_code = KRB5_KPASSWD_HARDERROR;654}655return ret;656}657658#ifndef HEIMDAL_SMALLER659660static struct kpwd_proc *661find_chpw_proto(const char *name)662{663struct kpwd_proc *p;664for (p = procs; p->name != NULL; p++) {665if (strcmp(p->name, name) == 0)666return p;667}668return NULL;669}670671/**672* Deprecated: krb5_change_password() is deprecated, use krb5_set_password().673*674* @param context a Keberos context675* @param creds676* @param newpw677* @param result_code678* @param result_code_string679* @param result_string680*681* @return On sucess password is changed.682683* @ingroup @krb5_deprecated684*/685686KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL687krb5_change_password (krb5_context context,688krb5_creds *creds,689const char *newpw,690int *result_code,691krb5_data *result_code_string,692krb5_data *result_string)693KRB5_DEPRECATED_FUNCTION("Use X instead")694{695struct kpwd_proc *p = find_chpw_proto("change password");696697*result_code = KRB5_KPASSWD_MALFORMED;698result_code_string->data = result_string->data = NULL;699result_code_string->length = result_string->length = 0;700701if (p == NULL)702return KRB5_KPASSWD_MALFORMED;703704return change_password_loop(context, creds, NULL, newpw,705result_code, result_code_string,706result_string, p);707}708#endif /* HEIMDAL_SMALLER */709710/**711* Change password using creds.712*713* @param context a Keberos context714* @param creds The initial kadmin/passwd for the principal or an admin principal715* @param newpw The new password to set716* @param targprinc if unset, the default principal is used.717* @param result_code Result code, KRB5_KPASSWD_SUCCESS is when password is changed.718* @param result_code_string binary message from the server, contains719* at least the result_code.720* @param result_string A message from the kpasswd service or the721* library in human printable form. The string is NUL terminated.722*723* @return On sucess and *result_code is KRB5_KPASSWD_SUCCESS, the password is changed.724725* @ingroup @krb5726*/727728KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL729krb5_set_password(krb5_context context,730krb5_creds *creds,731const char *newpw,732krb5_principal targprinc,733int *result_code,734krb5_data *result_code_string,735krb5_data *result_string)736{737krb5_principal principal = NULL;738krb5_error_code ret = 0;739int i;740741*result_code = KRB5_KPASSWD_MALFORMED;742krb5_data_zero(result_code_string);743krb5_data_zero(result_string);744745if (targprinc == NULL) {746ret = krb5_get_default_principal(context, &principal);747if (ret)748return ret;749} else750principal = targprinc;751752for (i = 0; procs[i].name != NULL; i++) {753*result_code = 0;754ret = change_password_loop(context, creds, principal, newpw,755result_code, result_code_string,756result_string,757&procs[i]);758if (ret == 0 && *result_code == 0)759break;760}761762if (targprinc == NULL)763krb5_free_principal(context, principal);764return ret;765}766767/*768*769*/770771KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL772krb5_set_password_using_ccache(krb5_context context,773krb5_ccache ccache,774const char *newpw,775krb5_principal targprinc,776int *result_code,777krb5_data *result_code_string,778krb5_data *result_string)779{780krb5_creds creds, *credsp;781krb5_error_code ret;782krb5_principal principal = NULL;783784*result_code = KRB5_KPASSWD_MALFORMED;785result_code_string->data = result_string->data = NULL;786result_code_string->length = result_string->length = 0;787788memset(&creds, 0, sizeof(creds));789790if (targprinc == NULL) {791ret = krb5_cc_get_principal(context, ccache, &principal);792if (ret)793return ret;794} else795principal = targprinc;796797ret = krb5_make_principal(context, &creds.server,798krb5_principal_get_realm(context, principal),799"kadmin", "changepw", NULL);800if (ret)801goto out;802803ret = krb5_cc_get_principal(context, ccache, &creds.client);804if (ret) {805krb5_free_principal(context, creds.server);806goto out;807}808809ret = krb5_get_credentials(context, 0, ccache, &creds, &credsp);810krb5_free_principal(context, creds.server);811krb5_free_principal(context, creds.client);812if (ret)813goto out;814815ret = krb5_set_password(context,816credsp,817newpw,818principal,819result_code,820result_code_string,821result_string);822823krb5_free_creds(context, credsp);824825return ret;826out:827if (targprinc == NULL)828krb5_free_principal(context, principal);829return ret;830}831832/*833*834*/835836KRB5_LIB_FUNCTION const char* KRB5_LIB_CALL837krb5_passwd_result_to_string (krb5_context context,838int result)839{840static const char *strings[] = {841"Success",842"Malformed",843"Hard error",844"Auth error",845"Soft error" ,846"Access denied",847"Bad version",848"Initial flag needed"849};850851if (result < 0 || result > KRB5_KPASSWD_INITIAL_FLAG_NEEDED)852return "unknown result code";853else854return strings[result];855}856857858