d84dd5dff9317897b8c4df49bb047b37240e7265
[samba.git] / source3 / libads / krb5_setpw.c
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 #define DEFAULT_KPASSWD_PORT    464
29
30 #define KRB5_KPASSWD_VERS_CHANGEPW              1
31
32 #define KRB5_KPASSWD_VERS_SETPW                 0xff80
33 #define KRB5_KPASSWD_VERS_SETPW_ALT             2
34
35 #define KRB5_KPASSWD_SUCCESS                    0
36 #define KRB5_KPASSWD_MALFORMED                  1
37 #define KRB5_KPASSWD_HARDERROR                  2
38 #define KRB5_KPASSWD_AUTHERROR                  3
39 #define KRB5_KPASSWD_SOFTERROR                  4
40 #define KRB5_KPASSWD_ACCESSDENIED               5
41 #define KRB5_KPASSWD_BAD_VERSION                6
42 #define KRB5_KPASSWD_INITIAL_FLAG_NEEDED        7
43
44 /* Those are defined by kerberos-set-passwd-02.txt and are probably 
45  * not supported by M$ implementation */
46 #define KRB5_KPASSWD_POLICY_REJECT              8
47 #define KRB5_KPASSWD_BAD_PRINCIPAL              9
48 #define KRB5_KPASSWD_ETYPE_NOSUPP               10
49
50 /*
51  * we've got to be able to distinguish KRB_ERRORs from other
52  * requests - valid response for CHPW v2 replies.
53  */
54
55 #define krb5_is_krb_error(packet) \
56         ( packet && packet->length && (((char *)packet->data)[0] == 0x7e || ((char *)packet->data)[0] == 0x5e))
57
58 /* This implements kerberos password change protocol as specified in 
59  * kerb-chg-password-02.txt and kerberos-set-passwd-02.txt
60  * as well as microsoft version of the protocol 
61  * as specified in kerberos-set-passwd-00.txt
62  */
63 static DATA_BLOB encode_krb5_setpw(const char *principal, const char *password)
64 {
65         char* princ_part1 = NULL;
66         char* princ_part2 = NULL;
67         char* realm = NULL;
68         char* c;
69         char* princ;
70
71         ASN1_DATA *req;
72         DATA_BLOB ret;
73
74
75         princ = SMB_STRDUP(principal);
76
77         if ((c = strchr_m(princ, '/')) == NULL) {
78                 c = princ; 
79         } else {
80                 *c = '\0';
81                 c++;
82                 princ_part1 = princ;
83         }
84
85         princ_part2 = c;
86
87         if ((c = strchr_m(c, '@')) != NULL) {
88                 *c = '\0';
89                 c++;
90                 realm = c;
91         } else {
92                 /* We must have a realm component. */
93                 return data_blob_null;
94         }
95
96         req = asn1_init(talloc_tos());
97         if (req == NULL) {
98                 return data_blob_null;
99         }
100
101         asn1_push_tag(req, ASN1_SEQUENCE(0));
102         asn1_push_tag(req, ASN1_CONTEXT(0));
103         asn1_write_OctetString(req, password, strlen(password));
104         asn1_pop_tag(req);
105
106         asn1_push_tag(req, ASN1_CONTEXT(1));
107         asn1_push_tag(req, ASN1_SEQUENCE(0));
108
109         asn1_push_tag(req, ASN1_CONTEXT(0));
110         asn1_write_Integer(req, 1);
111         asn1_pop_tag(req);
112
113         asn1_push_tag(req, ASN1_CONTEXT(1));
114         asn1_push_tag(req, ASN1_SEQUENCE(0));
115
116         if (princ_part1) {
117                 asn1_write_GeneralString(req, princ_part1);
118         }
119         
120         asn1_write_GeneralString(req, princ_part2);
121         asn1_pop_tag(req);
122         asn1_pop_tag(req);
123         asn1_pop_tag(req);
124         asn1_pop_tag(req);
125
126         asn1_push_tag(req, ASN1_CONTEXT(2));
127         asn1_write_GeneralString(req, realm);
128         asn1_pop_tag(req);
129         asn1_pop_tag(req);
130
131         ret = data_blob(req->data, req->length);
132         asn1_free(req);
133
134         free(princ);
135
136         return ret;
137 }       
138
139 static krb5_error_code build_kpasswd_request(uint16 pversion,
140                                            krb5_context context,
141                                            krb5_auth_context auth_context,
142                                            krb5_data *ap_req,
143                                            const char *princ,
144                                            const char *passwd,
145                                            bool use_tcp,
146                                            krb5_data *packet)
147 {
148         krb5_error_code ret;
149         krb5_data cipherpw;
150         krb5_data encoded_setpw;
151         krb5_replay_data replay;
152         char *p, *msg_start;
153         DATA_BLOB setpw;
154         unsigned int msg_length;
155
156         ret = krb5_auth_con_setflags(context,
157                                      auth_context,KRB5_AUTH_CONTEXT_DO_SEQUENCE);
158         if (ret) {
159                 DEBUG(1,("krb5_auth_con_setflags failed (%s)\n",
160                          error_message(ret)));
161                 return ret;
162         }
163
164         /* handle protocol differences in chpw and setpw */
165         if (pversion  == KRB5_KPASSWD_VERS_CHANGEPW)
166                 setpw = data_blob(passwd, strlen(passwd));
167         else if (pversion == KRB5_KPASSWD_VERS_SETPW ||
168                  pversion == KRB5_KPASSWD_VERS_SETPW_ALT)
169                 setpw = encode_krb5_setpw(princ, passwd);
170         else
171                 return EINVAL;
172
173         if (setpw.data == NULL || setpw.length == 0) {
174                 return EINVAL;
175         }
176
177         encoded_setpw.data = (char *)setpw.data;
178         encoded_setpw.length = setpw.length;
179
180         ret = krb5_mk_priv(context, auth_context,
181                            &encoded_setpw, &cipherpw, &replay);
182         
183         data_blob_free(&setpw);         /*from 'encode_krb5_setpw(...)' */
184         
185         if (ret) {
186                 DEBUG(1,("krb5_mk_priv failed (%s)\n", error_message(ret)));
187                 return ret;
188         }
189
190         packet->data = (char *)SMB_MALLOC(ap_req->length + cipherpw.length + (use_tcp ? 10 : 6 ));
191         if (!packet->data)
192                 return -1;
193
194
195
196         /* see the RFC for details */
197
198         msg_start = p = ((char *)packet->data) + (use_tcp ? 4 : 0);
199         p += 2;
200         RSSVAL(p, 0, pversion);
201         p += 2;
202         RSSVAL(p, 0, ap_req->length);
203         p += 2;
204         memcpy(p, ap_req->data, ap_req->length);
205         p += ap_req->length;
206         memcpy(p, cipherpw.data, cipherpw.length);
207         p += cipherpw.length;
208         packet->length = PTR_DIFF(p,packet->data);
209         msg_length = PTR_DIFF(p,msg_start);
210
211         if (use_tcp) {
212                 RSIVAL(packet->data, 0, msg_length);
213         }
214         RSSVAL(msg_start, 0, msg_length);
215         
216         free(cipherpw.data);    /* from 'krb5_mk_priv(...)' */
217
218         return 0;
219 }
220
221 static const struct kpasswd_errors {
222         int result_code;
223         const char *error_string;
224 } kpasswd_errors[] = {
225         {KRB5_KPASSWD_MALFORMED, "Malformed request error"},
226         {KRB5_KPASSWD_HARDERROR, "Server error"},
227         {KRB5_KPASSWD_AUTHERROR, "Authentication error"},
228         {KRB5_KPASSWD_SOFTERROR, "Password change rejected"},
229         {KRB5_KPASSWD_ACCESSDENIED, "Client does not have proper authorization"},
230         {KRB5_KPASSWD_BAD_VERSION, "Protocol version not supported"},
231         {KRB5_KPASSWD_INITIAL_FLAG_NEEDED, "Authorization ticket must have initial flag set"},
232         {KRB5_KPASSWD_POLICY_REJECT, "Password rejected due to policy requirements"},
233         {KRB5_KPASSWD_BAD_PRINCIPAL, "Target principal does not exist"},
234         {KRB5_KPASSWD_ETYPE_NOSUPP, "Unsupported encryption type"},
235         {0, NULL}
236 };
237
238 static krb5_error_code setpw_result_code_string(krb5_context context,
239                                                 int result_code,
240                                                 const char **code_string)
241 {
242         unsigned int idx = 0;
243
244         while (kpasswd_errors[idx].error_string != NULL) {
245                 if (kpasswd_errors[idx].result_code == 
246                     result_code) {
247                         *code_string = kpasswd_errors[idx].error_string;
248                         return 0;
249                 }
250                 idx++;
251         }
252         *code_string = "Password change failed";
253         return (0);
254 }
255
256  krb5_error_code kpasswd_err_to_krb5_err(krb5_error_code res_code) 
257 {
258         switch(res_code) {
259                 case KRB5_KPASSWD_ACCESSDENIED:
260                         return KRB5KDC_ERR_BADOPTION;
261                 case KRB5_KPASSWD_INITIAL_FLAG_NEEDED:
262                         return KRB5KDC_ERR_BADOPTION;
263                         /* return KV5M_ALT_METHOD; MIT-only define */
264                 case KRB5_KPASSWD_ETYPE_NOSUPP:
265                         return KRB5KDC_ERR_ETYPE_NOSUPP;
266                 case KRB5_KPASSWD_BAD_PRINCIPAL:
267                         return KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN;
268                 case KRB5_KPASSWD_POLICY_REJECT:
269                 case KRB5_KPASSWD_SOFTERROR:
270                         return KRB5KDC_ERR_POLICY;
271                 default:
272                         return KRB5KRB_ERR_GENERIC;
273         }
274 }
275 static krb5_error_code parse_setpw_reply(krb5_context context,
276                                          bool use_tcp,
277                                          krb5_auth_context auth_context,
278                                          krb5_data *packet)
279 {
280         krb5_data ap_rep;
281         char *p;
282         int vnum, ret, res_code;
283         krb5_data cipherresult;
284         krb5_data clearresult;
285         krb5_ap_rep_enc_part *ap_rep_enc;
286         krb5_replay_data replay;
287         unsigned int msg_length = packet->length;
288
289
290         if (packet->length < (use_tcp ? 8 : 4)) {
291                 return KRB5KRB_AP_ERR_MODIFIED;
292         }
293         
294         p = (char *)packet->data;
295         /*
296         ** see if it is an error
297         */
298         if (krb5_is_krb_error(packet)) {
299
300                 ret = handle_krberror_packet(context, packet);
301                 if (ret) {
302                         return ret;
303                 }
304         }
305
306         
307         /* tcp... */
308         if (use_tcp) {
309                 msg_length -= 4;
310                 if (RIVAL(p, 0) != msg_length) {
311                         DEBUG(1,("Bad TCP packet length (%d/%d) from kpasswd server\n",
312                         RIVAL(p, 0), msg_length));
313                         return KRB5KRB_AP_ERR_MODIFIED;
314                 }
315
316                 p += 4;
317         }
318         
319         if (RSVAL(p, 0) != msg_length) {
320                 DEBUG(1,("Bad packet length (%d/%d) from kpasswd server\n",
321                          RSVAL(p, 0), msg_length));
322                 return KRB5KRB_AP_ERR_MODIFIED;
323         }
324
325         p += 2;
326
327         vnum = RSVAL(p, 0); p += 2;
328
329         /* FIXME: According to standard there is only one type of reply */      
330         if (vnum != KRB5_KPASSWD_VERS_SETPW && 
331             vnum != KRB5_KPASSWD_VERS_SETPW_ALT && 
332             vnum != KRB5_KPASSWD_VERS_CHANGEPW) {
333                 DEBUG(1,("Bad vnum (%d) from kpasswd server\n", vnum));
334                 return KRB5KDC_ERR_BAD_PVNO;
335         }
336         
337         ap_rep.length = RSVAL(p, 0); p += 2;
338         
339         if (p + ap_rep.length >= (char *)packet->data + packet->length) {
340                 DEBUG(1,("ptr beyond end of packet from kpasswd server\n"));
341                 return KRB5KRB_AP_ERR_MODIFIED;
342         }
343         
344         if (ap_rep.length == 0) {
345                 DEBUG(1,("got unencrypted setpw result?!\n"));
346                 return KRB5KRB_AP_ERR_MODIFIED;
347         }
348
349         /* verify ap_rep */
350         ap_rep.data = p;
351         p += ap_rep.length;
352         
353         ret = krb5_rd_rep(context, auth_context, &ap_rep, &ap_rep_enc);
354         if (ret) {
355                 DEBUG(1,("failed to rd setpw reply (%s)\n", error_message(ret)));
356                 return KRB5KRB_AP_ERR_MODIFIED;
357         }
358         
359         krb5_free_ap_rep_enc_part(context, ap_rep_enc);
360         
361         cipherresult.data = p;
362         cipherresult.length = ((char *)packet->data + packet->length) - p;
363                 
364         ret = krb5_rd_priv(context, auth_context, &cipherresult, &clearresult,
365                            &replay);
366         if (ret) {
367                 DEBUG(1,("failed to decrypt setpw reply (%s)\n", error_message(ret)));
368                 return KRB5KRB_AP_ERR_MODIFIED;
369         }
370
371         if (clearresult.length < 2) {
372                 free(clearresult.data);
373                 ret = KRB5KRB_AP_ERR_MODIFIED;
374                 return KRB5KRB_AP_ERR_MODIFIED;
375         }
376         
377         p = (char *)clearresult.data;
378         
379         res_code = RSVAL(p, 0);
380         
381         free(clearresult.data);
382
383         if ((res_code < KRB5_KPASSWD_SUCCESS) || 
384             (res_code > KRB5_KPASSWD_ETYPE_NOSUPP)) {
385                 return KRB5KRB_AP_ERR_MODIFIED;
386         }
387
388         if (res_code == KRB5_KPASSWD_SUCCESS) {
389                 return 0;
390         } else {
391                 const char *errstr;
392                 setpw_result_code_string(context, res_code, &errstr);
393                 DEBUG(1, ("Error changing password: %s (%d)\n", errstr, res_code));
394
395                 return kpasswd_err_to_krb5_err(res_code);
396         }
397 }
398
399 static ADS_STATUS do_krb5_kpasswd_request(krb5_context context,
400                                           const char *kdc_host,
401                                           uint16 pversion,
402                                           krb5_creds *credsp,
403                                           const char *princ,
404                                           const char *newpw)
405 {
406         krb5_auth_context auth_context = NULL;
407         krb5_data ap_req, chpw_req, chpw_rep;
408         int ret, sock;
409         socklen_t addr_len;
410         struct sockaddr_storage remote_addr, local_addr;
411         struct sockaddr_storage addr;
412         krb5_address local_kaddr, remote_kaddr;
413         bool use_tcp = False;
414
415
416         if (!interpret_string_addr(&addr, kdc_host, 0)) {
417         }
418
419         ret = krb5_mk_req_extended(context, &auth_context, AP_OPTS_USE_SUBKEY,
420                                    NULL, credsp, &ap_req);
421         if (ret) {
422                 DEBUG(1,("krb5_mk_req_extended failed (%s)\n", error_message(ret)));
423                 return ADS_ERROR_KRB5(ret);
424         }
425
426         do {
427
428                 if (!use_tcp) {
429
430                         sock = open_udp_socket(kdc_host, DEFAULT_KPASSWD_PORT);
431                         if (sock == -1) {
432                                 int rc = errno;
433                                 SAFE_FREE(ap_req.data);
434                                 krb5_auth_con_free(context, auth_context);
435                                 DEBUG(1,("failed to open kpasswd socket to %s "
436                                          "(%s)\n", kdc_host, strerror(errno)));
437                                 return ADS_ERROR_SYSTEM(rc);
438                         }
439                 } else {
440                         NTSTATUS status;
441                         status = open_socket_out(&addr, DEFAULT_KPASSWD_PORT,
442                                                  LONG_CONNECT_TIMEOUT, &sock);
443                         if (!NT_STATUS_IS_OK(status)) {
444                                 SAFE_FREE(ap_req.data);
445                                 krb5_auth_con_free(context, auth_context);
446                                 DEBUG(1,("failed to open kpasswd socket to %s "
447                                          "(%s)\n", kdc_host,
448                                          nt_errstr(status)));
449                                 return ADS_ERROR_NT(status);
450                         }
451                 }
452
453                 addr_len = sizeof(remote_addr);
454                 if (getpeername(sock, (struct sockaddr *)&remote_addr, &addr_len) != 0) {
455                         close(sock);
456                         SAFE_FREE(ap_req.data);
457                         krb5_auth_con_free(context, auth_context);
458                         DEBUG(1,("getpeername() failed (%s)\n", error_message(errno)));
459                         return ADS_ERROR_SYSTEM(errno);
460                 }
461                 addr_len = sizeof(local_addr);
462                 if (getsockname(sock, (struct sockaddr *)&local_addr, &addr_len) != 0) {
463                         close(sock);
464                         SAFE_FREE(ap_req.data);
465                         krb5_auth_con_free(context, auth_context);
466                         DEBUG(1,("getsockname() failed (%s)\n", error_message(errno)));
467                         return ADS_ERROR_SYSTEM(errno);
468                 }
469                 if (!setup_kaddr(&remote_kaddr, &remote_addr) ||
470                                 !setup_kaddr(&local_kaddr, &local_addr)) {
471                         DEBUG(1,("do_krb5_kpasswd_request: "
472                                 "Failed to setup addresses.\n"));
473                         close(sock);
474                         SAFE_FREE(ap_req.data);
475                         krb5_auth_con_free(context, auth_context);
476                         errno = EINVAL;
477                         return ADS_ERROR_SYSTEM(EINVAL);
478                 }
479
480                 ret = krb5_auth_con_setaddrs(context, auth_context, &local_kaddr, NULL);
481                 if (ret) {
482                         close(sock);
483                         SAFE_FREE(ap_req.data);
484                         krb5_auth_con_free(context, auth_context);
485                         DEBUG(1,("krb5_auth_con_setaddrs failed (%s)\n", error_message(ret)));
486                         return ADS_ERROR_KRB5(ret);
487                 }
488
489                 ret = build_kpasswd_request(pversion, context, auth_context, &ap_req,
490                                           princ, newpw, use_tcp, &chpw_req);
491                 if (ret) {
492                         close(sock);
493                         SAFE_FREE(ap_req.data);
494                         krb5_auth_con_free(context, auth_context);
495                         DEBUG(1,("build_setpw_request failed (%s)\n", error_message(ret)));
496                         return ADS_ERROR_KRB5(ret);
497                 }
498
499                 ret = write(sock, chpw_req.data, chpw_req.length); 
500
501                 if (ret != chpw_req.length) {
502                         close(sock);
503                         SAFE_FREE(chpw_req.data);
504                         SAFE_FREE(ap_req.data);
505                         krb5_auth_con_free(context, auth_context);
506                         DEBUG(1,("send of chpw failed (%s)\n", strerror(errno)));
507                         return ADS_ERROR_SYSTEM(errno);
508                 }
509         
510                 SAFE_FREE(chpw_req.data);
511         
512                 chpw_rep.length = 1500;
513                 chpw_rep.data = (char *) SMB_MALLOC(chpw_rep.length);
514                 if (!chpw_rep.data) {
515                         close(sock);
516                         SAFE_FREE(ap_req.data);
517                         krb5_auth_con_free(context, auth_context);
518                         DEBUG(1,("send of chpw failed (%s)\n", strerror(errno)));
519                         errno = ENOMEM;
520                         return ADS_ERROR_SYSTEM(errno);
521                 }
522         
523                 ret = read(sock, chpw_rep.data, chpw_rep.length);
524                 if (ret < 0) {
525                         close(sock);
526                         SAFE_FREE(chpw_rep.data);
527                         SAFE_FREE(ap_req.data);
528                         krb5_auth_con_free(context, auth_context);
529                         DEBUG(1,("recv of chpw reply failed (%s)\n", strerror(errno)));
530                         return ADS_ERROR_SYSTEM(errno);
531                 }
532         
533                 close(sock);
534                 chpw_rep.length = ret;
535         
536                 ret = krb5_auth_con_setaddrs(context, auth_context, NULL,&remote_kaddr);
537                 if (ret) {
538                         SAFE_FREE(chpw_rep.data);
539                         SAFE_FREE(ap_req.data);
540                         krb5_auth_con_free(context, auth_context);
541                         DEBUG(1,("krb5_auth_con_setaddrs on reply failed (%s)\n", 
542                                  error_message(ret)));
543                         return ADS_ERROR_KRB5(ret);
544                 }
545         
546                 ret = parse_setpw_reply(context, use_tcp, auth_context, &chpw_rep);
547                 SAFE_FREE(chpw_rep.data);
548         
549                 if (ret) {
550                         
551                         if (ret == KRB5KRB_ERR_RESPONSE_TOO_BIG && !use_tcp) {
552                                 DEBUG(5, ("Trying setpw with TCP!!!\n"));
553                                 use_tcp = True;
554                                 continue;
555                         }
556
557                         SAFE_FREE(ap_req.data);
558                         krb5_auth_con_free(context, auth_context);
559                         DEBUG(1,("parse_setpw_reply failed (%s)\n", 
560                                  error_message(ret)));
561                         return ADS_ERROR_KRB5(ret);
562                 }
563         
564                 SAFE_FREE(ap_req.data);
565                 krb5_auth_con_free(context, auth_context);
566         } while ( ret );
567
568         return ADS_SUCCESS;
569 }
570
571 ADS_STATUS ads_krb5_set_password(const char *kdc_host, const char *princ, 
572                                  const char *newpw, int time_offset)
573 {
574
575         ADS_STATUS aret;
576         krb5_error_code ret = 0;
577         krb5_context context = NULL;
578         const char *realm = NULL;
579         unsigned int realm_len = 0;
580         krb5_creds creds, *credsp = NULL;
581         krb5_ccache ccache = NULL;
582
583         ZERO_STRUCT(creds);
584         
585         initialize_krb5_error_table();
586         ret = krb5_init_context(&context);
587         if (ret) {
588                 DEBUG(1,("Failed to init krb5 context (%s)\n", error_message(ret)));
589                 return ADS_ERROR_KRB5(ret);
590         }
591         
592         if (time_offset != 0) {
593                 krb5_set_real_time(context, time(NULL) + time_offset, 0);
594         }
595
596         ret = krb5_cc_default(context, &ccache);
597         if (ret) {
598                 krb5_free_context(context);
599                 DEBUG(1,("Failed to get default creds (%s)\n", error_message(ret)));
600                 return ADS_ERROR_KRB5(ret);
601         }
602
603         ret = krb5_cc_get_principal(context, ccache, &creds.client);
604         if (ret) {
605                 krb5_cc_close(context, ccache);
606                 krb5_free_context(context);
607                 DEBUG(1,("Failed to get principal from ccache (%s)\n",
608                          error_message(ret)));
609                 return ADS_ERROR_KRB5(ret);
610         }
611
612         realm = smb_krb5_principal_get_realm(context, creds.client);
613         realm_len = strlen(realm);
614         ret = krb5_build_principal(context,
615                                    &creds.server,
616                                    realm_len,
617                                    realm, "kadmin", "changepw", NULL);
618
619         ret = krb5_get_credentials(context, 0, ccache, &creds, &credsp);
620         if (ret) {
621                 krb5_cc_close(context, ccache);
622                 krb5_free_principal(context, creds.client);
623                 krb5_free_principal(context, creds.server);
624                 krb5_free_context(context);
625                 DEBUG(1,("krb5_build_prinipal_ext (%s)\n", error_message(ret)));
626                 return ADS_ERROR_KRB5(ret);
627         }
628         
629         ret = krb5_get_credentials(context, 0, ccache, &creds, &credsp); 
630         if (ret) {
631                 krb5_cc_close(context, ccache);
632                 krb5_free_principal(context, creds.client);
633                 krb5_free_principal(context, creds.server);
634                 krb5_free_context(context);
635                 DEBUG(1,("krb5_get_credentials failed (%s)\n", error_message(ret)));
636                 return ADS_ERROR_KRB5(ret);
637         }
638         
639         /* we might have to call krb5_free_creds(...) from now on ... */
640
641         aret = do_krb5_kpasswd_request(context, kdc_host,
642                                        KRB5_KPASSWD_VERS_SETPW,
643                                        credsp, princ, newpw);
644
645         krb5_free_creds(context, credsp);
646         krb5_free_principal(context, creds.client);
647         krb5_free_principal(context, creds.server);
648         krb5_cc_close(context, ccache);
649         krb5_free_context(context);
650
651         return aret;
652 }
653
654 /*
655   we use a prompter to avoid a crash bug in the kerberos libs when 
656   dealing with empty passwords
657   this prompter is just a string copy ...
658 */
659 static krb5_error_code 
660 kerb_prompter(krb5_context ctx, void *data,
661                const char *name,
662                const char *banner,
663                int num_prompts,
664                krb5_prompt prompts[])
665 {
666         if (num_prompts == 0) return 0;
667
668         memset(prompts[0].reply->data, 0, prompts[0].reply->length);
669         if (prompts[0].reply->length > 0) {
670                 if (data) {
671                         strncpy((char *)prompts[0].reply->data,
672                                 (const char *)data,
673                                 prompts[0].reply->length-1);
674                         prompts[0].reply->length = strlen((const char *)prompts[0].reply->data);
675                 } else {
676                         prompts[0].reply->length = 0;
677                 }
678         }
679         return 0;
680 }
681
682 static ADS_STATUS ads_krb5_chg_password(const char *kdc_host,
683                                         const char *principal,
684                                         const char *oldpw, 
685                                         const char *newpw, 
686                                         int time_offset)
687 {
688     ADS_STATUS aret;
689     krb5_error_code ret;
690     krb5_context context = NULL;
691     krb5_principal princ;
692     krb5_get_init_creds_opt opts;
693     krb5_creds creds;
694     char *chpw_princ = NULL, *password;
695     const char *realm = NULL;
696
697     initialize_krb5_error_table();
698     ret = krb5_init_context(&context);
699     if (ret) {
700         DEBUG(1,("Failed to init krb5 context (%s)\n", error_message(ret)));
701         return ADS_ERROR_KRB5(ret);
702     }
703
704     if ((ret = smb_krb5_parse_name(context, principal,
705                                     &princ))) {
706         krb5_free_context(context);
707         DEBUG(1,("Failed to parse %s (%s)\n", principal, error_message(ret)));
708         return ADS_ERROR_KRB5(ret);
709     }
710
711     krb5_get_init_creds_opt_init(&opts);
712     krb5_get_init_creds_opt_set_tkt_life(&opts, 5*60);
713     krb5_get_init_creds_opt_set_renew_life(&opts, 0);
714     krb5_get_init_creds_opt_set_forwardable(&opts, 0);
715     krb5_get_init_creds_opt_set_proxiable(&opts, 0);
716
717     realm = smb_krb5_principal_get_realm(context, princ);
718
719     /* We have to obtain an INITIAL changepw ticket for changing password */
720     if (asprintf(&chpw_princ, "kadmin/changepw@%s", realm) == -1) {
721         krb5_free_context(context);
722         DEBUG(1,("ads_krb5_chg_password: asprintf fail\n"));
723         return ADS_ERROR_NT(NT_STATUS_NO_MEMORY);
724     }
725
726     password = SMB_STRDUP(oldpw);
727     ret = krb5_get_init_creds_password(context, &creds, princ, password,
728                                            kerb_prompter, NULL, 
729                                            0, chpw_princ, &opts);
730     SAFE_FREE(chpw_princ);
731     SAFE_FREE(password);
732
733     if (ret) {
734       if (ret == KRB5KRB_AP_ERR_BAD_INTEGRITY)
735         DEBUG(1,("Password incorrect while getting initial ticket"));
736       else
737         DEBUG(1,("krb5_get_init_creds_password failed (%s)\n", error_message(ret)));
738
739         krb5_free_principal(context, princ);
740         krb5_free_context(context);
741         return ADS_ERROR_KRB5(ret);
742     }
743
744     aret = do_krb5_kpasswd_request(context, kdc_host,
745                                    KRB5_KPASSWD_VERS_CHANGEPW,
746                                    &creds, principal, newpw);
747
748     krb5_free_principal(context, princ);
749     krb5_free_context(context);
750
751     return aret;
752 }
753
754
755 ADS_STATUS kerberos_set_password(const char *kpasswd_server, 
756                                  const char *auth_principal, const char *auth_password,
757                                  const char *target_principal, const char *new_password,
758                                  int time_offset)
759 {
760     int ret;
761
762     if ((ret = kerberos_kinit_password(auth_principal, auth_password, time_offset, NULL))) {
763         DEBUG(1,("Failed kinit for principal %s (%s)\n", auth_principal, error_message(ret)));
764         return ADS_ERROR_KRB5(ret);
765     }
766
767     if (!strcmp(auth_principal, target_principal))
768         return ads_krb5_chg_password(kpasswd_server, target_principal,
769                                      auth_password, new_password, time_offset);
770     else
771         return ads_krb5_set_password(kpasswd_server, target_principal,
772                                      new_password, time_offset);
773 }
774
775 #endif