1/*
2 * Copyright (c) 1997 - 2005 Kungliga Tekniska Högskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 *
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 *
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 *
17 * 3. Neither the name of the Institute nor the names of its contributors
18 *    may be used to endorse or promote products derived from this software
19 *    without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
32 */
33
34#include "krb5_locl.h"
35
36struct request {
37    krb5_auth_context ac;
38    krb5_creds *creds;
39    krb5_principal target;
40    const char *password;
41};
42
43/*
44 * Change password protocol defined by
45 * draft-ietf-cat-kerb-chg-password-02.txt
46 *
47 * Share the response part of the protocol with MS set password
48 * (RFC3244)
49 */
50
51static krb5_error_code
52chgpw_prexmit(krb5_context context, int proto,
53	      void *ctx, rk_socket_t fd, krb5_data *data)
54{
55    struct request *request = ctx;
56    krb5_data ap_req_data, krb_priv_data, passwd_data;
57    krb5_storage *sp = NULL;
58    krb5_error_code ret;
59    krb5_ssize_t slen;
60    size_t len;
61
62    krb5_data_zero(&ap_req_data);
63    krb5_data_zero(&krb_priv_data);
64
65    ret = krb5_auth_con_genaddrs(context, request->ac, fd,
66				 KRB5_AUTH_CONTEXT_GENERATE_LOCAL_ADDR);
67    if (ret)
68	goto out;
69
70    ret = krb5_mk_req_extended(context,
71			       &request->ac,
72			       AP_OPTS_MUTUAL_REQUIRED | AP_OPTS_USE_SUBKEY,
73			       NULL,
74			       request->creds,
75			       &ap_req_data);
76    if (ret)
77	goto out;
78
79    passwd_data.data   = rk_UNCONST(request->password);
80    passwd_data.length = strlen(request->password);
81
82    ret = krb5_mk_priv(context,
83		       request->ac,
84		       &passwd_data,
85		       &krb_priv_data,
86		       NULL);
87    if (ret)
88	goto out;
89
90    sp = krb5_storage_emem();
91    if (sp == NULL) {
92	ret = ENOMEM;
93	goto out;
94    }
95
96    len = 6 + ap_req_data.length + krb_priv_data.length;
97
98    ret = krb5_store_uint16(sp, len);
99    if (ret) goto out;
100    ret = krb5_store_uint16(sp, 1);
101    if (ret) goto out;
102    ret = krb5_store_uint16(sp, ap_req_data.length);
103    if (ret) goto out;
104    slen = krb5_storage_write(sp, ap_req_data.data, ap_req_data.length);
105    if (slen != ap_req_data.length) {
106	ret = EINVAL;
107	goto out;
108    }
109    slen = krb5_storage_write(sp, krb_priv_data.data, krb_priv_data.length);
110    if (slen != krb_priv_data.length) {
111	ret = EINVAL;
112	goto out;
113    }
114
115    ret = krb5_storage_to_data(sp, data);
116
117 out:
118    if (ret)
119	_krb5_debugx(context, 10, "chgpw_prexmit failed with: %d", ret);
120    if (sp)
121	krb5_storage_free(sp);
122    krb5_data_free(&krb_priv_data);
123    krb5_data_free(&ap_req_data);
124    return ret;
125}
126
127/*
128 * Set password protocol as defined by RFC3244 --
129 * Microsoft Windows 2000 Kerberos Change Password and Set Password Protocols
130 */
131
132static krb5_error_code
133setpw_prexmit(krb5_context context, int proto,
134	      void *ctx, int fd, krb5_data *data)
135{
136    struct request *request = ctx;
137    krb5_data ap_req_data, krb_priv_data, pwd_data;
138    krb5_error_code ret;
139    ChangePasswdDataMS chpw;
140    krb5_storage *sp = NULL;
141    ssize_t slen;
142    size_t len;
143
144    krb5_data_zero(&ap_req_data);
145    krb5_data_zero(&krb_priv_data);
146    krb5_data_zero(&pwd_data);
147
148    ret = krb5_auth_con_genaddrs(context, request->ac, fd,
149				 KRB5_AUTH_CONTEXT_GENERATE_LOCAL_ADDR);
150    if (ret)
151	goto out;
152
153    ret = krb5_mk_req_extended(context,
154			       &request->ac,
155			       AP_OPTS_MUTUAL_REQUIRED | AP_OPTS_USE_SUBKEY,
156			       NULL,
157			       request->creds,
158			       &ap_req_data);
159    if (ret)
160	goto out;
161
162    chpw.newpasswd.length = strlen(request->password);
163    chpw.newpasswd.data = rk_UNCONST(request->password);
164    if (request->target) {
165	chpw.targname = &request->target->name;
166	chpw.targrealm = &request->target->realm;
167    } else {
168	chpw.targname = NULL;
169	chpw.targrealm = NULL;
170    }
171
172    ASN1_MALLOC_ENCODE(ChangePasswdDataMS, pwd_data.data, pwd_data.length,
173		       &chpw, &len, ret);
174    if (ret)
175	goto out;
176    if(pwd_data.length != len)
177	krb5_abortx(context, "internal error in ASN.1 encoder");
178
179    ret = krb5_mk_priv (context,
180			request->ac,
181			&pwd_data,
182			&krb_priv_data,
183			NULL);
184    if (ret)
185	goto out;
186
187    sp = krb5_storage_emem();
188    if (sp == NULL) {
189	ret = ENOMEM;
190	goto out;
191    }
192
193    len = 6 + ap_req_data.length + krb_priv_data.length;
194
195    ret = krb5_store_uint16(sp, len);
196    if (ret) goto out;
197    ret = krb5_store_uint16(sp, 0xff80);
198    if (ret) goto out;
199    ret = krb5_store_uint16(sp, ap_req_data.length);
200    if (ret) goto out;
201    slen = krb5_storage_write(sp, ap_req_data.data, ap_req_data.length);
202    if (slen != ap_req_data.length) {
203	ret = EINVAL;
204	goto out;
205    }
206    slen = krb5_storage_write(sp, krb_priv_data.data, krb_priv_data.length);
207    if (slen != krb_priv_data.length) {
208	ret = EINVAL;
209	goto out;
210    }
211
212    ret = krb5_storage_to_data(sp, data);
213
214 out:
215    if (ret)
216	_krb5_debugx(context, 10, "setpw_prexmit failed with %d", ret);
217    if (sp)
218	krb5_storage_free(sp);
219    krb5_data_free(&krb_priv_data);
220    krb5_data_free(&ap_req_data);
221    krb5_data_free(&pwd_data);
222
223    return ret;
224}
225
226static krb5_error_code
227process_reply(krb5_context context,
228	      krb5_auth_context auth_context,
229	      krb5_data *data,
230	      int *result_code,
231	      krb5_data *result_code_string,
232	      krb5_data *result_string)
233{
234    krb5_error_code ret;
235    ssize_t len;
236    uint16_t pkt_len, pkt_ver;
237    krb5_data ap_rep_data;
238    uint8_t *reply;
239
240    krb5_auth_con_clear(context, auth_context,
241			KRB5_AUTH_CONTEXT_CLEAR_LOCAL_ADDR|KRB5_AUTH_CONTEXT_CLEAR_REMOTE_ADDR);
242    len = data->length;
243    reply = data->data;;
244
245    if (len < 6) {
246	krb5_data_format(result_string, "server sent to too short message "
247			 "(%ld bytes)", (long)len);
248	*result_code = KRB5_KPASSWD_MALFORMED;
249	return 0;
250    }
251
252    pkt_len = (reply[0] << 8) | (reply[1]);
253    pkt_ver = (reply[2] << 8) | (reply[3]);
254
255    if ((pkt_len != len) || (reply[1] == 0x7e || reply[1] == 0x5e)) {
256	KRB_ERROR error;
257	size_t size;
258	u_char *p;
259
260	memset(&error, 0, sizeof(error));
261
262	ret = decode_KRB_ERROR(reply, len, &error, &size);
263	if (ret)
264	    return ret;
265
266	if (error.e_data->length < 2) {
267	    krb5_data_format(result_string, "server sent too short "
268		     "e_data to print anything usable");
269	    free_KRB_ERROR(&error);
270	    *result_code = KRB5_KPASSWD_MALFORMED;
271	    return 0;
272	}
273
274	p = error.e_data->data;
275	*result_code = (p[0] << 8) | p[1];
276	if (error.e_data->length == 2)
277	    krb5_data_format(result_string, "server only sent error code");
278	else
279	    krb5_data_copy (result_string,
280			    p + 2,
281			    error.e_data->length - 2);
282	free_KRB_ERROR(&error);
283	return 0;
284    }
285
286    if (pkt_len != len) {
287	krb5_data_format(result_string, "client: wrong len in reply");
288	*result_code = KRB5_KPASSWD_MALFORMED;
289	return 0;
290    }
291    if (pkt_ver != KRB5_KPASSWD_VERS_CHANGEPW) {
292	krb5_data_format(result_string,
293		  "client: wrong version number (%d)", pkt_ver);
294	*result_code = KRB5_KPASSWD_MALFORMED;
295	return 0;
296    }
297
298    ap_rep_data.data = reply + 6;
299    ap_rep_data.length  = (reply[4] << 8) | (reply[5]);
300
301    if (reply + len < (u_char *)ap_rep_data.data + ap_rep_data.length) {
302	krb5_data_format(result_string, "client: wrong AP len in reply");
303	*result_code = KRB5_KPASSWD_MALFORMED;
304	return 0;
305    }
306
307    if (ap_rep_data.length) {
308	krb5_ap_rep_enc_part *ap_rep;
309	krb5_data priv_data;
310	u_char *p;
311
312	priv_data.data   = (u_char*)ap_rep_data.data + ap_rep_data.length;
313	priv_data.length = len - ap_rep_data.length - 6;
314
315	ret = krb5_rd_rep (context,
316			   auth_context,
317			   &ap_rep_data,
318			   &ap_rep);
319	if (ret)
320	    return ret;
321
322	krb5_free_ap_rep_enc_part (context, ap_rep);
323
324	ret = krb5_rd_priv (context,
325			    auth_context,
326			    &priv_data,
327			    result_code_string,
328			    NULL);
329	if (ret) {
330	    krb5_data_free (result_code_string);
331	    return ret;
332	}
333
334	if (result_code_string->length < 2) {
335	    *result_code = KRB5_KPASSWD_MALFORMED;
336	    krb5_data_format(result_string,
337		      "client: bad length in result");
338	    return 0;
339	}
340
341        p = result_code_string->data;
342
343        *result_code = (p[0] << 8) | p[1];
344        krb5_data_copy (result_string,
345                        (unsigned char*)result_code_string->data + 2,
346                        result_code_string->length - 2);
347        return 0;
348    } else {
349	KRB_ERROR error;
350	size_t size;
351	u_char *p;
352
353	ret = decode_KRB_ERROR(reply + 6, len - 6, &error, &size);
354	if (ret) {
355	    return ret;
356	}
357	if (error.e_data->length < 2) {
358	    krb5_warnx (context, "too short e_data to print anything usable");
359	    return 1;		/* XXX */
360	}
361
362	p = error.e_data->data;
363	*result_code = (p[0] << 8) | p[1];
364	krb5_data_copy (result_string,
365			p + 2,
366			error.e_data->length - 2);
367	return 0;
368    }
369}
370
371
372/*
373 * change the password using the credentials in `creds' (for the
374 * principal indicated in them) to `newpw', storing the result of
375 * the operation in `result_*' and an error code or 0.
376 */
377
378typedef krb5_error_code (*kpwd_process_reply) (krb5_context,
379					       krb5_auth_context,
380					       krb5_data *data,
381					       int *,
382					       krb5_data *,
383					       krb5_data *);
384
385static struct kpwd_proc {
386    const char *name;
387    int flags;
388#define SUPPORT_TCP	1
389#define SUPPORT_UDP	2
390#define SUPPORT_ADMIN	4
391    krb5_sendto_prexmit prexmit;
392    kpwd_process_reply process_rep;
393} procs[] = {
394    {
395	"MS set password",
396	SUPPORT_TCP|SUPPORT_UDP|SUPPORT_ADMIN,
397	setpw_prexmit,
398	process_reply
399    },
400    {
401	"change password",
402	SUPPORT_UDP,
403	chgpw_prexmit,
404	process_reply
405    },
406    { NULL, 0, NULL, NULL }
407};
408
409/*
410 *
411 */
412
413static krb5_error_code
414change_password_loop(krb5_context	context,
415		     struct request *request,
416		     int		*result_code,
417		     krb5_data		*result_code_string,
418		     krb5_data		*result_string,
419		     struct kpwd_proc	*proc)
420{
421    krb5_error_code ret;
422    krb5_data zero, zero2;
423    krb5_sendto_ctx ctx = NULL;
424    krb5_realm realm;
425
426    krb5_data_zero(&zero);
427    krb5_data_zero(&zero2);
428
429    if (request->target)
430	realm = request->target->realm;
431    else
432	realm = request->creds->client->realm;
433
434    _krb5_debugx(context, 1, "trying to set password using: %s in realm %s",
435		proc->name, realm);
436
437    ret = krb5_auth_con_init(context, &request->ac);
438    if (ret)
439	goto out;
440
441    krb5_auth_con_setflags(context, request->ac, KRB5_AUTH_CONTEXT_DO_SEQUENCE);
442
443    ret = krb5_sendto_ctx_alloc(context, &ctx);
444    if (ret)
445	goto out;
446
447    krb5_sendto_ctx_set_type(ctx, KRB5_KRBHST_CHANGEPW);
448
449    /* XXX this is a evil hack */
450    if (request->creds->ticket.length > 700) {
451	_krb5_debugx(context, 1, "using TCP since the ticket is large: %lu",
452		    (unsigned long)request->creds->ticket.length);
453	krb5_sendto_ctx_add_flags(ctx, KRB5_KRBHST_FLAGS_LARGE_MSG);
454    }
455
456    _krb5_sendto_ctx_set_prexmit(ctx, proc->prexmit, request);
457
458    ret = krb5_sendto_context(context, ctx, &zero, realm, &zero2);
459
460    if (ret == 0)
461	ret = proc->process_rep(context, request->ac, &zero2,
462				result_code, result_code_string, result_string);
463
464 out:
465    _krb5_debugx(context, 1, "set password using %s returned: %d result_code %d",
466		 proc->name, ret, *result_code);
467
468    krb5_auth_con_free(context, request->ac);
469    if (ctx)
470	krb5_sendto_ctx_free(context, ctx);
471
472    krb5_data_free(&zero2);
473
474    if (ret == KRB5_KDC_UNREACH) {
475	krb5_set_error_message(context,
476			       ret,
477			       N_("Unable to reach any changepw server "
478				 " in realm %s", "realm"), realm);
479	*result_code = KRB5_KPASSWD_HARDERROR;
480    }
481    return ret;
482}
483
484#ifndef HEIMDAL_SMALLER
485
486static struct kpwd_proc *
487find_chpw_proto(const char *name)
488{
489    struct kpwd_proc *p;
490    for (p = procs; p->name != NULL; p++) {
491	if (strcmp(p->name, name) == 0)
492	    return p;
493    }
494    return NULL;
495}
496
497/**
498 * Deprecated: krb5_change_password() is deprecated, use krb5_set_password().
499 *
500 * @param context a Keberos context
501 * @param creds
502 * @param newpw
503 * @param result_code
504 * @param result_code_string
505 * @param result_string
506 *
507 * @return On sucess password is changed.
508
509 * @ingroup @krb5_deprecated
510 */
511
512KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
513krb5_change_password (krb5_context	context,
514		      krb5_creds	*creds,
515		      const char	*newpw,
516		      int		*result_code,
517		      krb5_data		*result_code_string,
518		      krb5_data		*result_string)
519    KRB5_DEPRECATED_FUNCTION("Use X instead")
520{
521    struct kpwd_proc *p = find_chpw_proto("change password");
522    struct request request;
523
524    *result_code = KRB5_KPASSWD_MALFORMED;
525    result_code_string->data = result_string->data = NULL;
526    result_code_string->length = result_string->length = 0;
527
528    if (p == NULL)
529	return KRB5_KPASSWD_MALFORMED;
530
531    request.ac = NULL;
532    request.target = targprinc;
533    request.creds = creds;
534    request.password = password;
535
536    return change_password_loop(context, &request,
537				result_code, result_code_string,
538				result_string, p);
539}
540#endif /* HEIMDAL_SMALLER */
541
542/**
543 * Change password using creds.
544 *
545 * @param context a Keberos context
546 * @param creds The initial kadmin/passwd for the principal or an admin principal
547 * @param newpw The new password to set
548 * @param targprinc For most callers should pass NULL in this
549 *        argument. Targprinc is the principal to change the password
550 *        for. This argument should only be set when you want to
551 *        change another Kerberos principal's password, ie you are an
552 *        admin. If NULL, the default authenticating principal in the
553 *        creds argument is used instead.
554 * @param result_code Result code, KRB5_KPASSWD_SUCCESS is when password is changed.
555 * @param result_code_string binary message from the server, contains
556 * at least the result_code.
557 * @param result_string A message from the kpasswd service or the
558 * library in human printable form. The string is NUL terminated.
559 *
560 * @return On sucess and *result_code is KRB5_KPASSWD_SUCCESS, the password is changed.
561
562 * @ingroup @krb5
563 */
564
565KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
566krb5_set_password(krb5_context context,
567		  krb5_creds *creds,
568		  const char *newpw,
569		  krb5_principal targprinc,
570		  int *result_code,
571		  krb5_data *result_code_string,
572		  krb5_data *result_string)
573{
574    krb5_error_code ret = 0;
575    struct request request;
576    int i;
577
578    *result_code = KRB5_KPASSWD_MALFORMED;
579    krb5_data_zero(result_code_string);
580    krb5_data_zero(result_string);
581
582    _krb5_debugx(context, 1, "trying to set password");
583
584    request.ac = NULL;
585    request.target = targprinc;
586    request.creds = creds;
587    request.password = newpw;
588
589    for (i = 0; procs[i].name != NULL; i++) {
590	/* don't try methods that don't support admind change password */
591	if (targprinc && (procs[i].flags & SUPPORT_ADMIN) == 0)
592	    continue;
593
594	ret = change_password_loop(context, &request,
595				   result_code, result_code_string,
596				   result_string,
597				   &procs[i]);
598	if (ret == 0 && *result_code == 0)
599	    break;
600    }
601
602    return ret;
603}
604
605/**
606 * Change password using a credential cache that contains an initial
607 * credential or an admin credential.
608 *
609 * @param context a Keberos context
610 * @param ccache the credential cache to use to find the
611 *        kadmin/changepw principal.
612 * @param newpw The new password to set
613 * @param targprinc For most callers should pass NULL in this
614 *        argument. Targprinc is the principal to change the password
615 *        for. This argument should only be set when you want to
616 *        change another Kerberos principal's password, ie you are an
617 *        admin. If NULL, the default authenticating principal in the
618 *        creds argument is used instead.
619 * @param result_code Result code, KRB5_KPASSWD_SUCCESS is when password is changed.
620 * @param result_code_string binary message from the server, contains
621 * at least the result_code.
622 * @param result_string A message from the kpasswd service or the
623 * library in human printable form. The string is NUL terminated.
624 *
625 * @return On sucess and *result_code is KRB5_KPASSWD_SUCCESS, the password is changed.
626 *
627 */
628
629KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
630krb5_set_password_using_ccache(krb5_context context,
631			       krb5_ccache ccache,
632			       const char *newpw,
633			       krb5_principal targprinc,
634			       int *result_code,
635			       krb5_data *result_code_string,
636			       krb5_data *result_string)
637{
638    krb5_creds creds, *credsp = NULL;
639    krb5_error_code ret;
640    krb5_principal principal = NULL;
641
642    *result_code = KRB5_KPASSWD_MALFORMED;
643    krb5_data_zero(result_code_string);
644    krb5_data_zero(result_string);
645
646    memset(&creds, 0, sizeof(creds));
647
648    if (targprinc == NULL) {
649	ret = krb5_cc_get_principal(context, ccache, &principal);
650	if (ret)
651	    return ret;
652    } else
653	principal = targprinc;
654
655    ret = krb5_make_principal(context, &creds.server,
656			      krb5_principal_get_realm(context, principal),
657			      "kadmin", "changepw", NULL);
658    if (ret)
659	goto out;
660
661    ret = krb5_cc_get_principal(context, ccache, &creds.client);
662    if (ret) {
663        krb5_free_principal(context, creds.server);
664	goto out;
665    }
666
667    ret = krb5_get_credentials(context, 0, ccache, &creds, &credsp);
668    krb5_free_principal(context, creds.server);
669    krb5_free_principal(context, creds.client);
670    if (ret)
671	goto out;
672
673    ret = krb5_set_password(context,
674			    credsp,
675			    newpw,
676			    targprinc,
677			    result_code,
678			    result_code_string,
679			    result_string);
680
681 out:
682    if (credsp)
683	krb5_free_creds(context, credsp);
684    if (targprinc == NULL)
685	krb5_free_principal(context, principal);
686    return ret;
687}
688
689/*
690 *
691 */
692
693KRB5_LIB_FUNCTION const char* KRB5_LIB_CALL
694krb5_passwd_result_to_string (krb5_context context,
695			      int result)
696{
697    static const char *strings[] = {
698	"Success",
699	"Malformed",
700	"Hard error",
701	"Auth error",
702	"Soft error" ,
703	"Access denied",
704	"Bad version",
705	"Initial flag needed"
706    };
707
708    if (result < 0 || result > KRB5_KPASSWD_INITIAL_FLAG_NEEDED)
709	return "unknown result code";
710    else
711	return strings[result];
712}
713