LCOV - code coverage report
Current view: top level - source3/libads - krb5_setpw.c (source / functions) Hit Total Coverage
Test: coverage report for master 98b443d9 Lines: 93 185 50.3 %
Date: 2024-05-31 13:13:24 Functions: 3 5 60.0 %

          Line data    Source code
       1             : /* 
       2             :    Unix SMB/CIFS implementation.
       3             :    krb5 set password implementation
       4             :    Copyright (C) Andrew Tridgell 2001
       5             :    Copyright (C) Remus Koos 2001 (remuskoos@yahoo.com)
       6             :    
       7             :    This program is free software; you can redistribute it and/or modify
       8             :    it under the terms of the GNU General Public License as published by
       9             :    the Free Software Foundation; either version 3 of the License, or
      10             :    (at your option) any later version.
      11             :    
      12             :    This program is distributed in the hope that it will be useful,
      13             :    but WITHOUT ANY WARRANTY; without even the implied warranty of
      14             :    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      15             :    GNU General Public License for more details.
      16             :    
      17             :    You should have received a copy of the GNU General Public License
      18             :    along with this program.  If not, see <http://www.gnu.org/licenses/>.
      19             : */
      20             : 
      21             : #include "includes.h"
      22             : #include "smb_krb5.h"
      23             : #include "libads/kerberos_proto.h"
      24             : #include "../lib/util/asn1.h"
      25             : 
      26             : #ifdef HAVE_KRB5
      27             : 
      28             : /* Those are defined by kerberos-set-passwd-02.txt and are probably
      29             :  * not supported by M$ implementation */
      30             : #define KRB5_KPASSWD_POLICY_REJECT              8
      31             : #define KRB5_KPASSWD_BAD_PRINCIPAL              9
      32             : #define KRB5_KPASSWD_ETYPE_NOSUPP               10
      33             : 
      34             : /*
      35             :  * we've got to be able to distinguish KRB_ERRORs from other
      36             :  * requests - valid response for CHPW v2 replies.
      37             :  */
      38             : 
      39           0 : static krb5_error_code kpasswd_err_to_krb5_err(krb5_error_code res_code)
      40             : {
      41           0 :         switch (res_code) {
      42           0 :         case KRB5_KPASSWD_ACCESSDENIED:
      43           0 :                 return KRB5KDC_ERR_BADOPTION;
      44           0 :         case KRB5_KPASSWD_INITIAL_FLAG_NEEDED:
      45           0 :                 return KRB5KDC_ERR_BADOPTION;
      46             :                 /* return KV5M_ALT_METHOD; MIT-only define */
      47           0 :         case KRB5_KPASSWD_ETYPE_NOSUPP:
      48           0 :                 return KRB5KDC_ERR_ETYPE_NOSUPP;
      49           0 :         case KRB5_KPASSWD_BAD_PRINCIPAL:
      50           0 :                 return KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN;
      51           0 :         case KRB5_KPASSWD_POLICY_REJECT:
      52             :         case KRB5_KPASSWD_SOFTERROR:
      53           0 :                 return KRB5KDC_ERR_POLICY;
      54           0 :         default:
      55           0 :                 return KRB5KRB_ERR_GENERIC;
      56             :         }
      57             : }
      58             : 
      59           6 : ADS_STATUS ads_krb5_set_password(const char *principal,
      60             :                                  const char *newpw,
      61             :                                  const char *ccname)
      62             : {
      63             : 
      64           0 :         ADS_STATUS aret;
      65           6 :         krb5_error_code ret = 0;
      66           6 :         krb5_context context = NULL;
      67           6 :         krb5_principal princ = NULL;
      68           6 :         krb5_ccache ccache = NULL;
      69           0 :         int result_code;
      70           6 :         krb5_data result_code_string = { 0 };
      71           6 :         krb5_data result_string = { 0 };
      72             : 
      73           6 :         if (ccname == NULL) {
      74           0 :                 DBG_ERR("Missing ccache for [%s] and config [%s]\n",
      75             :                         principal, getenv("KRB5_CONFIG"));
      76           0 :                 return ADS_ERROR_NT(NT_STATUS_WRONG_CREDENTIAL_HANDLE);
      77             :         }
      78             : 
      79           6 :         ret = smb_krb5_init_context_common(&context);
      80           6 :         if (ret) {
      81           0 :                 DBG_ERR("kerberos init context failed (%s)\n",
      82             :                         error_message(ret));
      83           0 :                 return ADS_ERROR_KRB5(ret);
      84             :         }
      85             : 
      86           6 :         if (principal) {
      87           6 :                 ret = smb_krb5_parse_name(context, principal, &princ);
      88           6 :                 if (ret) {
      89           0 :                         krb5_free_context(context);
      90           0 :                         DEBUG(1, ("Failed to parse %s (%s)\n", principal,
      91             :                                   error_message(ret)));
      92           0 :                         return ADS_ERROR_KRB5(ret);
      93             :                 }
      94             :         }
      95             : 
      96           6 :         ret = krb5_cc_resolve(context, ccname, &ccache);
      97           6 :         if (ret) {
      98           0 :                 krb5_free_principal(context, princ);
      99           0 :                 krb5_free_context(context);
     100           0 :                 DBG_WARNING("Failed to get creds from [%s] (%s)\n",
     101             :                             ccname, error_message(ret));
     102           0 :                 return ADS_ERROR_KRB5(ret);
     103             :         }
     104             : 
     105           6 :         ret = krb5_set_password_using_ccache(context,
     106             :                                              ccache,
     107             :                                              discard_const_p(char, newpw),
     108             :                                              princ,
     109             :                                              &result_code,
     110             :                                              &result_code_string,
     111             :                                              &result_string);
     112           6 :         if (ret) {
     113           0 :                 DEBUG(1, ("krb5_set_password failed (%s)\n", error_message(ret)));
     114           0 :                 aret = ADS_ERROR_KRB5(ret);
     115           0 :                 goto done;
     116             :         }
     117             : 
     118           6 :         if (result_code != KRB5_KPASSWD_SUCCESS) {
     119           0 :                 ret = kpasswd_err_to_krb5_err(result_code);
     120           0 :                 DEBUG(1, ("krb5_set_password failed (%s)\n", error_message(ret)));
     121           0 :                 aret = ADS_ERROR_KRB5(ret);
     122           0 :                 goto done;
     123             :         }
     124             : 
     125           6 :         aret = ADS_SUCCESS;
     126             : 
     127           6 :  done:
     128           6 :         smb_krb5_free_data_contents(context, &result_code_string);
     129           6 :         smb_krb5_free_data_contents(context, &result_string);
     130           6 :         krb5_free_principal(context, princ);
     131           6 :         krb5_cc_close(context, ccache);
     132           6 :         krb5_free_context(context);
     133             : 
     134           6 :         return aret;
     135             : }
     136             : 
     137             : /*
     138             :   we use a prompter to avoid a crash bug in the kerberos libs when 
     139             :   dealing with empty passwords
     140             :   this prompter is just a string copy ...
     141             : */
     142             : static krb5_error_code 
     143           0 : kerb_prompter(krb5_context ctx, void *data,
     144             :                const char *name,
     145             :                const char *banner,
     146             :                int num_prompts,
     147             :                krb5_prompt prompts[])
     148             : {
     149           0 :         if (num_prompts == 0) return 0;
     150             : 
     151           0 :         memset(prompts[0].reply->data, 0, prompts[0].reply->length);
     152           0 :         if (prompts[0].reply->length > 0) {
     153           0 :                 if (data) {
     154           0 :                         strncpy((char *)prompts[0].reply->data,
     155             :                                 (const char *)data,
     156           0 :                                 prompts[0].reply->length-1);
     157           0 :                         prompts[0].reply->length = strlen((const char *)prompts[0].reply->data);
     158             :                 } else {
     159           0 :                         prompts[0].reply->length = 0;
     160             :                 }
     161             :         }
     162           0 :         return 0;
     163             : }
     164             : 
     165           6 : static ADS_STATUS ads_krb5_chg_password(const char *principal,
     166             :                                         const char *oldpw,
     167             :                                         const char *newpw)
     168             : {
     169           0 :         ADS_STATUS aret;
     170           0 :         krb5_error_code ret;
     171           6 :         krb5_context context = NULL;
     172           0 :         krb5_principal princ;
     173           6 :         krb5_get_init_creds_opt *opts = NULL;
     174           0 :         krb5_creds creds;
     175           6 :         char *chpw_princ = NULL, *password;
     176           6 :         char *realm = NULL;
     177           0 :         int result_code;
     178           6 :         krb5_data result_code_string = { 0 };
     179           6 :         krb5_data result_string = { 0 };
     180           6 :         smb_krb5_addresses *addr = NULL;
     181             : 
     182           6 :         ret = smb_krb5_init_context_common(&context);
     183           6 :         if (ret) {
     184           0 :                 DBG_ERR("kerberos init context failed (%s)\n",
     185             :                         error_message(ret));
     186           0 :                 return ADS_ERROR_KRB5(ret);
     187             :         }
     188             : 
     189           6 :         if ((ret = smb_krb5_parse_name(context, principal, &princ))) {
     190           0 :                 krb5_free_context(context);
     191           0 :                 DEBUG(1,("Failed to parse %s (%s)\n", principal, error_message(ret)));
     192           0 :                 return ADS_ERROR_KRB5(ret);
     193             :         }
     194             : 
     195           6 :         ret = krb5_get_init_creds_opt_alloc(context, &opts);
     196           6 :         if (ret != 0) {
     197           0 :                 krb5_free_context(context);
     198           0 :                 DBG_WARNING("krb5_get_init_creds_opt_alloc failed: %s\n",
     199             :                             error_message(ret));
     200           0 :                 return ADS_ERROR_KRB5(ret);
     201             :         }
     202             : 
     203           6 :         krb5_get_init_creds_opt_set_tkt_life(opts, 5 * 60);
     204           6 :         krb5_get_init_creds_opt_set_renew_life(opts, 0);
     205           6 :         krb5_get_init_creds_opt_set_forwardable(opts, 0);
     206           6 :         krb5_get_init_creds_opt_set_proxiable(opts, 0);
     207             : #ifdef SAMBA4_USES_HEIMDAL
     208           3 :         krb5_get_init_creds_opt_set_win2k(context, opts, true);
     209           3 :         krb5_get_init_creds_opt_set_canonicalize(context, opts, true);
     210             : #else /* MIT */
     211             : #if 0
     212             :         /*
     213             :          * FIXME
     214             :          *
     215             :          * Due to an upstream MIT Kerberos bug, this feature is not
     216             :          * not working. Affection versions (2019-10-09): <= 1.17
     217             :          *
     218             :          * Reproducer:
     219             :          * kinit -C aDmInIsTrAtOr@ACME.COM -S kadmin/changepw@ACME.COM
     220             :          *
     221             :          * This is NOT a problem if the service is a krbtgt.
     222             :          *
     223             :          * https://bugzilla.samba.org/show_bug.cgi?id=14155
     224             :          */
     225             :         krb5_get_init_creds_opt_set_canonicalize(opts, true);
     226             : #endif
     227             : #endif /* MIT */
     228             : 
     229             :         /* note that heimdal will fill in the local addresses if the addresses
     230             :          * in the creds_init_opt are all empty and then later fail with invalid
     231             :          * address, sending our local netbios krb5 address - just like windows
     232             :          * - avoids this - gd */
     233           6 :         ret = smb_krb5_gen_netbios_krb5_address(&addr, lp_netbios_name());
     234           6 :         if (ret) {
     235           0 :                 krb5_free_principal(context, princ);
     236           0 :                 krb5_get_init_creds_opt_free(context, opts);
     237           0 :                 krb5_free_context(context);
     238           0 :                 return ADS_ERROR_KRB5(ret);
     239             :         }
     240           6 :         krb5_get_init_creds_opt_set_address_list(opts, addr->addrs);
     241             : 
     242           6 :         realm = smb_krb5_principal_get_realm(NULL, context, princ);
     243             : 
     244             :         /* We have to obtain an INITIAL changepw ticket for changing password */
     245           6 :         if (asprintf(&chpw_princ, "kadmin/changepw@%s", realm) == -1) {
     246           0 :                 krb5_free_principal(context, princ);
     247           0 :                 krb5_get_init_creds_opt_free(context, opts);
     248           0 :                 smb_krb5_free_addresses(context, addr);
     249           0 :                 krb5_free_context(context);
     250           0 :                 TALLOC_FREE(realm);
     251           0 :                 DEBUG(1, ("ads_krb5_chg_password: asprintf fail\n"));
     252           0 :                 return ADS_ERROR_NT(NT_STATUS_NO_MEMORY);
     253             :         }
     254             : 
     255           6 :         TALLOC_FREE(realm);
     256           6 :         password = SMB_STRDUP(oldpw);
     257           6 :         ret = krb5_get_init_creds_password(context, &creds, princ, password,
     258             :                                            kerb_prompter, NULL,
     259             :                                            0, chpw_princ, opts);
     260           6 :         krb5_get_init_creds_opt_free(context, opts);
     261           6 :         smb_krb5_free_addresses(context, addr);
     262           6 :         SAFE_FREE(chpw_princ);
     263           6 :         SAFE_FREE(password);
     264             : 
     265           6 :         if (ret) {
     266           0 :                 if (ret == KRB5KRB_AP_ERR_BAD_INTEGRITY) {
     267           0 :                         DEBUG(1,("Password incorrect while getting initial ticket\n"));
     268             :                 } else {
     269           0 :                         DEBUG(1,("krb5_get_init_creds_password failed (%s)\n", error_message(ret)));
     270             :                 }
     271           0 :                 krb5_free_principal(context, princ);
     272           0 :                 krb5_free_context(context);
     273           0 :                 return ADS_ERROR_KRB5(ret);
     274             :         }
     275             : 
     276           6 :         ret = krb5_set_password(context,
     277             :                                 &creds,
     278             :                                 discard_const_p(char, newpw),
     279             :                                 NULL,
     280             :                                 &result_code,
     281             :                                 &result_code_string,
     282             :                                 &result_string);
     283             : 
     284           6 :         if (ret) {
     285           0 :                 DEBUG(1, ("krb5_change_password failed (%s)\n", error_message(ret)));
     286           0 :                 aret = ADS_ERROR_KRB5(ret);
     287           0 :                 goto done;
     288             :         }
     289             : 
     290           6 :         if (result_code != KRB5_KPASSWD_SUCCESS) {
     291           0 :                 ret = kpasswd_err_to_krb5_err(result_code);
     292           0 :                 DEBUG(1, ("krb5_change_password failed (%s)\n", error_message(ret)));
     293           0 :                 aret = ADS_ERROR_KRB5(ret);
     294           0 :                 goto done;
     295             :         }
     296             : 
     297           6 :         aret = ADS_SUCCESS;
     298             : 
     299           6 :  done:
     300           6 :         smb_krb5_free_data_contents(context, &result_code_string);
     301           6 :         smb_krb5_free_data_contents(context, &result_string);
     302           6 :         krb5_free_principal(context, princ);
     303           6 :         krb5_free_context(context);
     304             : 
     305           6 :         return aret;
     306             : }
     307             : 
     308           8 : ADS_STATUS kerberos_set_password(const char *auth_principal,
     309             :                                  const char *auth_password,
     310             :                                  const char *target_principal,
     311             :                                  const char *new_password)
     312             : {
     313           8 :         TALLOC_CTX *frame = NULL;
     314           8 :         krb5_context ctx = NULL;
     315           8 :         krb5_ccache ccid = NULL;
     316           8 :         char *ccname = NULL;
     317           0 :         ADS_STATUS status;
     318           0 :         int ret;
     319             : 
     320           8 :         if (strcmp(auth_principal, target_principal) == 0) {
     321             :                 /*
     322             :                  * kinit is done inside of ads_krb5_chg_password()
     323             :                  * without any ccache, just with raw krb5_creds.
     324             :                  */
     325           6 :                 return ads_krb5_chg_password(target_principal,
     326             :                                              auth_password,
     327             :                                              new_password);
     328             :         }
     329             : 
     330           2 :         frame = talloc_stackframe();
     331             : 
     332           2 :         ret = smb_krb5_init_context_common(&ctx);
     333           2 :         if (ret != 0) {
     334           0 :                 status = ADS_ERROR_KRB5(ret);
     335           0 :                 goto done;
     336             :         }
     337             : 
     338           2 :         ret = smb_krb5_cc_new_unique_memory(ctx,
     339             :                                             frame,
     340             :                                             &ccname,
     341             :                                             &ccid);
     342           2 :         if (ret != 0) {
     343           0 :                 status = ADS_ERROR_KRB5(ret);
     344           0 :                 goto done;
     345             :         }
     346             : 
     347           2 :         ret = kerberos_kinit_password(auth_principal,
     348             :                                       auth_password,
     349             :                                       0, /* timeoutset */
     350             :                                       ccname);
     351           2 :         if (ret != 0) {
     352           0 :                 DBG_ERR("Failed kinit for principal %s (%s)\n",
     353             :                         auth_principal, error_message(ret));
     354           0 :                 status = ADS_ERROR_KRB5(ret);
     355           0 :                 goto done;
     356             :         }
     357             : 
     358           2 :         status = ads_krb5_set_password(target_principal,
     359             :                                        new_password,
     360             :                                        ccname);
     361           2 :         if (!ADS_ERR_OK(status)) {
     362           0 :                 DBG_ERR("Failed to set password for %s as %s: %s\n",
     363             :                         target_principal,
     364             :                         auth_principal,
     365             :                         ads_errstr(status));
     366           0 :                 goto done;
     367             :         }
     368             : 
     369           2 : done:
     370           2 :         if (ccid != NULL) {
     371           2 :                 krb5_cc_destroy(ctx, ccid);
     372           2 :                 ccid = NULL;
     373             :         }
     374           2 :         if (ctx != NULL) {
     375           2 :                 krb5_free_context(ctx);
     376           2 :                 ctx = NULL;
     377             :         }
     378           2 :         TALLOC_FREE(frame);
     379           2 :         return status;
     380             : }
     381             : 
     382             : #endif

Generated by: LCOV version 1.14