This commit was manufactured by cvs2svn to create tag
[heimdal.git] / lib / krb5 / changepw.c
blob8f5ff6b1777a431b6f308cec1c940118ba268fe7
1 /*
2 * Copyright (c) 1997 - 2003 Kungliga Tekniska Högskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
4 * All rights reserved.
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
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.
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.
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.
34 #include <krb5_locl.h>
36 RCSID("$Id$");
38 static void
39 str2data (krb5_data *d,
40 const char *fmt,
41 ...) __attribute__ ((format (printf, 2, 3)));
43 static void
44 str2data (krb5_data *d,
45 const char *fmt,
46 ...)
48 va_list args;
50 va_start(args, fmt);
51 d->length = vasprintf ((char **)&d->data, fmt, args);
52 va_end(args);
56 * Change password protocol defined by
57 * draft-ietf-cat-kerb-chg-password-02.txt
59 * Share the response part of the protocol with MS set password
60 * (RFC3244)
63 static krb5_error_code
64 chgpw_send_request (krb5_context context,
65 krb5_auth_context *auth_context,
66 krb5_creds *creds,
67 krb5_principal targprinc,
68 int is_stream,
69 int sock,
70 char *passwd,
71 const char *host)
73 krb5_error_code ret;
74 krb5_data ap_req_data;
75 krb5_data krb_priv_data;
76 krb5_data passwd_data;
77 size_t len;
78 u_char header[6];
79 u_char *p;
80 struct iovec iov[3];
81 struct msghdr msghdr;
83 if (is_stream)
84 return KRB5_KPASSWD_MALFORMED;
86 if (targprinc &&
87 krb5_principal_compare(context, creds->client, targprinc) != TRUE)
88 return KRB5_KPASSWD_MALFORMED;
90 krb5_data_zero (&ap_req_data);
92 ret = krb5_mk_req_extended (context,
93 auth_context,
94 AP_OPTS_MUTUAL_REQUIRED | AP_OPTS_USE_SUBKEY,
95 NULL, /* in_data */
96 creds,
97 &ap_req_data);
98 if (ret)
99 return ret;
101 passwd_data.data = passwd;
102 passwd_data.length = strlen(passwd);
104 krb5_data_zero (&krb_priv_data);
106 ret = krb5_mk_priv (context,
107 *auth_context,
108 &passwd_data,
109 &krb_priv_data,
110 NULL);
111 if (ret)
112 goto out2;
114 len = 6 + ap_req_data.length + krb_priv_data.length;
115 p = header;
116 *p++ = (len >> 8) & 0xFF;
117 *p++ = (len >> 0) & 0xFF;
118 *p++ = 0;
119 *p++ = 1;
120 *p++ = (ap_req_data.length >> 8) & 0xFF;
121 *p++ = (ap_req_data.length >> 0) & 0xFF;
123 memset(&msghdr, 0, sizeof(msghdr));
124 msghdr.msg_name = NULL;
125 msghdr.msg_namelen = 0;
126 msghdr.msg_iov = iov;
127 msghdr.msg_iovlen = sizeof(iov)/sizeof(*iov);
128 #if 0
129 msghdr.msg_control = NULL;
130 msghdr.msg_controllen = 0;
131 #endif
133 iov[0].iov_base = (void*)header;
134 iov[0].iov_len = 6;
135 iov[1].iov_base = ap_req_data.data;
136 iov[1].iov_len = ap_req_data.length;
137 iov[2].iov_base = krb_priv_data.data;
138 iov[2].iov_len = krb_priv_data.length;
140 if (sendmsg (sock, &msghdr, 0) < 0) {
141 ret = errno;
142 krb5_set_error_string(context, "sendmsg %s: %s", host, strerror(ret));
145 krb5_data_free (&krb_priv_data);
146 out2:
147 krb5_data_free (&ap_req_data);
148 return ret;
152 * Set password protocol as defined by RFC3244 --
153 * Microsoft Windows 2000 Kerberos Change Password and Set Password Protocols
156 static krb5_error_code
157 setpw_send_request (krb5_context context,
158 krb5_auth_context *auth_context,
159 krb5_creds *creds,
160 krb5_principal targprinc,
161 int is_stream,
162 int sock,
163 char *passwd,
164 const char *host)
166 krb5_error_code ret;
167 krb5_data ap_req_data;
168 krb5_data krb_priv_data;
169 krb5_data pwd_data;
170 ChangePasswdDataMS chpw;
171 size_t len;
172 u_char header[4 + 6];
173 u_char *p;
174 struct iovec iov[3];
175 struct msghdr msghdr;
177 krb5_data_zero (&ap_req_data);
179 ret = krb5_mk_req_extended (context,
180 auth_context,
181 AP_OPTS_MUTUAL_REQUIRED | AP_OPTS_USE_SUBKEY,
182 NULL, /* in_data */
183 creds,
184 &ap_req_data);
185 if (ret)
186 return ret;
188 chpw.newpasswd.length = strlen(passwd);
189 chpw.newpasswd.data = passwd;
190 if (targprinc) {
191 chpw.targname = &targprinc->name;
192 chpw.targrealm = &targprinc->realm;
193 } else {
194 chpw.targname = NULL;
195 chpw.targrealm = NULL;
198 ASN1_MALLOC_ENCODE(ChangePasswdDataMS, pwd_data.data, pwd_data.length,
199 &chpw, &len, ret);
200 if (ret) {
201 krb5_data_free (&ap_req_data);
202 return ret;
205 if(pwd_data.length != len)
206 krb5_abortx(context, "internal error in ASN.1 encoder");
208 ret = krb5_mk_priv (context,
209 *auth_context,
210 &pwd_data,
211 &krb_priv_data,
212 NULL);
213 if (ret)
214 goto out2;
216 len = 6 + ap_req_data.length + krb_priv_data.length;
217 p = header;
218 if (is_stream) {
219 _krb5_put_int(p, len, 4);
220 p += 4;
222 *p++ = (len >> 8) & 0xFF;
223 *p++ = (len >> 0) & 0xFF;
224 *p++ = 0xff;
225 *p++ = 0x80;
226 *p++ = (ap_req_data.length >> 8) & 0xFF;
227 *p++ = (ap_req_data.length >> 0) & 0xFF;
229 memset(&msghdr, 0, sizeof(msghdr));
230 msghdr.msg_name = NULL;
231 msghdr.msg_namelen = 0;
232 msghdr.msg_iov = iov;
233 msghdr.msg_iovlen = sizeof(iov)/sizeof(*iov);
234 #if 0
235 msghdr.msg_control = NULL;
236 msghdr.msg_controllen = 0;
237 #endif
239 iov[0].iov_base = (void*)header;
240 if (is_stream)
241 iov[0].iov_len = 10;
242 else
243 iov[0].iov_len = 6;
244 iov[1].iov_base = ap_req_data.data;
245 iov[1].iov_len = ap_req_data.length;
246 iov[2].iov_base = krb_priv_data.data;
247 iov[2].iov_len = krb_priv_data.length;
249 if (sendmsg (sock, &msghdr, 0) < 0) {
250 ret = errno;
251 krb5_set_error_string(context, "sendmsg %s: %s", host, strerror(ret));
254 krb5_data_free (&krb_priv_data);
255 out2:
256 krb5_data_free (&ap_req_data);
257 krb5_data_free (&pwd_data);
258 return ret;
261 static krb5_error_code
262 process_reply (krb5_context context,
263 krb5_auth_context auth_context,
264 int is_stream,
265 int sock,
266 int *result_code,
267 krb5_data *result_code_string,
268 krb5_data *result_string,
269 const char *host)
271 krb5_error_code ret;
272 u_char reply[1024 * 3];
273 ssize_t len;
274 u_int16_t pkt_len, pkt_ver;
275 krb5_data ap_rep_data;
276 int save_errno;
278 len = 0;
279 if (is_stream) {
280 while (len < sizeof(reply)) {
281 unsigned long size;
283 ret = recvfrom (sock, reply + len, sizeof(reply) - len,
284 0, NULL, NULL);
285 if (ret < 0) {
286 save_errno = errno;
287 krb5_set_error_string(context, "recvfrom %s: %s",
288 host, strerror(save_errno));
289 return save_errno;
290 } else if (ret == 0) {
291 krb5_set_error_string(context, "recvfrom timeout %s", host);
292 return 1;
294 len += ret;
295 if (len < 4)
296 continue;
297 _krb5_get_int(reply, &size, 4);
298 if (size + 4 < len)
299 continue;
300 memmove(reply, reply + 4, size);
301 len = size;
302 break;
304 if (len == sizeof(reply)) {
305 krb5_set_error_string(context, "message too large from %s",
306 host);
307 return ENOMEM;
309 } else {
310 ret = recvfrom (sock, reply, sizeof(reply), 0, NULL, NULL);
311 if (ret < 0) {
312 save_errno = errno;
313 krb5_set_error_string(context, "recvfrom %s: %s",
314 host, strerror(save_errno));
315 return save_errno;
317 len = ret;
320 if (len < 6) {
321 str2data (result_string, "server %s sent to too short message "
322 "(%d bytes)", host, len);
323 *result_code = KRB5_KPASSWD_MALFORMED;
324 return 0;
327 pkt_len = (reply[0] << 8) | (reply[1]);
328 pkt_ver = (reply[2] << 8) | (reply[3]);
330 if ((pkt_len != len) || (reply[1] == 0x7e || reply[1] == 0x5e)) {
331 KRB_ERROR error;
332 size_t size;
333 u_char *p;
335 memset(&error, 0, sizeof(error));
337 ret = decode_KRB_ERROR(reply, len, &error, &size);
338 if (ret)
339 return ret;
341 if (error.e_data->length < 2) {
342 str2data(result_string, "server %s sent too short "
343 "e_data to print anything usable", host);
344 free_KRB_ERROR(&error);
345 *result_code = KRB5_KPASSWD_MALFORMED;
346 return 0;
349 p = error.e_data->data;
350 *result_code = (p[0] << 8) | p[1];
351 if (error.e_data->length == 2)
352 str2data(result_string, "server only sent error code");
353 else
354 krb5_data_copy (result_string,
355 p + 2,
356 error.e_data->length - 2);
357 free_KRB_ERROR(&error);
358 return 0;
361 if (pkt_len != len) {
362 str2data (result_string, "client: wrong len in reply");
363 *result_code = KRB5_KPASSWD_MALFORMED;
364 return 0;
366 if (pkt_ver != KRB5_KPASSWD_VERS_CHANGEPW) {
367 str2data (result_string,
368 "client: wrong version number (%d)", pkt_ver);
369 *result_code = KRB5_KPASSWD_MALFORMED;
370 return 0;
373 ap_rep_data.data = reply + 6;
374 ap_rep_data.length = (reply[4] << 8) | (reply[5]);
376 if (reply + len < (u_char *)ap_rep_data.data + ap_rep_data.length) {
377 str2data (result_string, "client: wrong AP len in reply");
378 *result_code = KRB5_KPASSWD_MALFORMED;
379 return 0;
382 if (ap_rep_data.length) {
383 krb5_ap_rep_enc_part *ap_rep;
384 krb5_data priv_data;
385 u_char *p;
387 priv_data.data = (u_char*)ap_rep_data.data + ap_rep_data.length;
388 priv_data.length = len - ap_rep_data.length - 6;
390 ret = krb5_rd_rep (context,
391 auth_context,
392 &ap_rep_data,
393 &ap_rep);
394 if (ret)
395 return ret;
397 krb5_free_ap_rep_enc_part (context, ap_rep);
399 ret = krb5_rd_priv (context,
400 auth_context,
401 &priv_data,
402 result_code_string,
403 NULL);
404 if (ret) {
405 krb5_data_free (result_code_string);
406 return ret;
409 if (result_code_string->length < 2) {
410 *result_code = KRB5_KPASSWD_MALFORMED;
411 str2data (result_string,
412 "client: bad length in result");
413 return 0;
416 p = result_code_string->data;
418 *result_code = (p[0] << 8) | p[1];
419 krb5_data_copy (result_string,
420 (unsigned char*)result_code_string->data + 2,
421 result_code_string->length - 2);
422 return 0;
423 } else {
424 KRB_ERROR error;
425 size_t size;
426 u_char *p;
428 ret = decode_KRB_ERROR(reply + 6, len - 6, &error, &size);
429 if (ret) {
430 return ret;
432 if (error.e_data->length < 2) {
433 krb5_warnx (context, "too short e_data to print anything usable");
434 return 1; /* XXX */
437 p = error.e_data->data;
438 *result_code = (p[0] << 8) | p[1];
439 krb5_data_copy (result_string,
440 p + 2,
441 error.e_data->length - 2);
442 return 0;
448 * change the password using the credentials in `creds' (for the
449 * principal indicated in them) to `newpw', storing the result of
450 * the operation in `result_*' and an error code or 0.
453 typedef krb5_error_code (*kpwd_send_request) (krb5_context,
454 krb5_auth_context *,
455 krb5_creds *,
456 krb5_principal,
457 int,
458 int,
459 char *,
460 const char *);
461 typedef krb5_error_code (*kpwd_process_reply) (krb5_context,
462 krb5_auth_context,
463 int,
464 int,
465 int *,
466 krb5_data *,
467 krb5_data *,
468 const char *);
470 struct kpwd_proc {
471 const char *name;
472 int flags;
473 #define SUPPORT_TCP 1
474 #define SUPPORT_UDP 2
475 kpwd_send_request send_req;
476 kpwd_process_reply process_rep;
477 } procs[] = {
479 "MS set password",
480 SUPPORT_TCP|SUPPORT_UDP,
481 setpw_send_request,
482 process_reply
485 "change password",
486 SUPPORT_UDP,
487 chgpw_send_request,
488 process_reply
490 { NULL }
493 static struct kpwd_proc *
494 find_chpw_proto(const char *name)
496 struct kpwd_proc *p;
497 for (p = procs; p->name != NULL; p++) {
498 if (strcmp(p->name, name) == 0)
499 return p;
501 return NULL;
508 static krb5_error_code
509 change_password_loop (krb5_context context,
510 krb5_creds *creds,
511 krb5_principal targprinc,
512 char *newpw,
513 int *result_code,
514 krb5_data *result_code_string,
515 krb5_data *result_string,
516 struct kpwd_proc *proc)
518 krb5_error_code ret;
519 krb5_auth_context auth_context = NULL;
520 krb5_krbhst_handle handle = NULL;
521 krb5_krbhst_info *hi;
522 int sock;
523 int i;
524 int done = 0;
525 krb5_realm realm = creds->client->realm;
527 ret = krb5_auth_con_init (context, &auth_context);
528 if (ret)
529 return ret;
531 krb5_auth_con_setflags (context, auth_context,
532 KRB5_AUTH_CONTEXT_DO_SEQUENCE);
534 ret = krb5_krbhst_init (context, realm, KRB5_KRBHST_CHANGEPW, &handle);
535 if (ret)
536 goto out;
538 while (!done && (ret = krb5_krbhst_next(context, handle, &hi)) == 0) {
539 struct addrinfo *ai, *a;
540 int is_stream;
542 switch (hi->proto) {
543 case KRB5_KRBHST_UDP:
544 if ((proc->flags & SUPPORT_UDP) == 0)
545 continue;
546 is_stream = 0;
547 break;
548 case KRB5_KRBHST_TCP:
549 if ((proc->flags & SUPPORT_TCP) == 0)
550 continue;
551 is_stream = 1;
552 break;
553 default:
554 continue;
557 ret = krb5_krbhst_get_addrinfo(context, hi, &ai);
558 if (ret)
559 continue;
561 for (a = ai; !done && a != NULL; a = a->ai_next) {
562 int replied = 0;
564 sock = socket (a->ai_family, a->ai_socktype, a->ai_protocol);
565 if (sock < 0)
566 continue;
568 ret = connect(sock, a->ai_addr, a->ai_addrlen);
569 if (ret < 0) {
570 close (sock);
571 goto out;
574 ret = krb5_auth_con_genaddrs (context, auth_context, sock,
575 KRB5_AUTH_CONTEXT_GENERATE_LOCAL_ADDR);
576 if (ret) {
577 close (sock);
578 goto out;
581 for (i = 0; !done && i < 5; ++i) {
582 fd_set fdset;
583 struct timeval tv;
585 if (!replied) {
586 replied = 0;
588 ret = (*proc->send_req) (context,
589 &auth_context,
590 creds,
591 targprinc,
592 is_stream,
593 sock,
594 newpw,
595 hi->hostname);
596 if (ret) {
597 close(sock);
598 goto out;
602 if (sock >= FD_SETSIZE) {
603 krb5_set_error_string(context, "fd %d too large", sock);
604 ret = ERANGE;
605 close (sock);
606 goto out;
609 FD_ZERO(&fdset);
610 FD_SET(sock, &fdset);
611 tv.tv_usec = 0;
612 tv.tv_sec = 1 + (1 << i);
614 ret = select (sock + 1, &fdset, NULL, NULL, &tv);
615 if (ret < 0 && errno != EINTR) {
616 close(sock);
617 goto out;
619 if (ret == 1) {
620 ret = (*proc->process_rep) (context,
621 auth_context,
622 is_stream,
623 sock,
624 result_code,
625 result_code_string,
626 result_string,
627 hi->hostname);
628 if (ret == 0)
629 done = 1;
630 else if (i > 0 && ret == KRB5KRB_AP_ERR_MUT_FAIL)
631 replied = 1;
632 } else {
633 ret = KRB5_KDC_UNREACH;
636 close (sock);
640 out:
641 krb5_krbhst_free (context, handle);
642 krb5_auth_con_free (context, auth_context);
643 if (done)
644 return 0;
645 else {
646 if (ret == KRB5_KDC_UNREACH)
647 krb5_set_error_string(context,
648 "unable to reach any changepw server "
649 " in realm %s", realm);
650 return ret;
656 * change the password using the credentials in `creds' (for the
657 * principal indicated in them) to `newpw', storing the result of
658 * the operation in `result_*' and an error code or 0.
661 krb5_error_code
662 krb5_change_password (krb5_context context,
663 krb5_creds *creds,
664 char *newpw,
665 int *result_code,
666 krb5_data *result_code_string,
667 krb5_data *result_string)
669 struct kpwd_proc *p = find_chpw_proto("change password");
671 *result_code = KRB5_KPASSWD_MALFORMED;
672 result_code_string->data = result_string->data = NULL;
673 result_code_string->length = result_string->length = 0;
675 if (p == NULL)
676 return KRB5_KPASSWD_MALFORMED;
678 return change_password_loop(context, creds, NULL, newpw,
679 result_code, result_code_string,
680 result_string, p);
687 krb5_error_code
688 krb5_set_password(krb5_context context,
689 krb5_creds *creds,
690 char *newpw,
691 krb5_principal targprinc,
692 int *result_code,
693 krb5_data *result_code_string,
694 krb5_data *result_string)
696 krb5_principal principal = NULL;
697 krb5_error_code ret = 0;
698 int i;
700 *result_code = KRB5_KPASSWD_MALFORMED;
701 result_code_string->data = result_string->data = NULL;
702 result_code_string->length = result_string->length = 0;
704 if (targprinc == NULL) {
705 ret = krb5_get_default_principal(context, &principal);
706 if (ret)
707 return ret;
708 } else
709 principal = targprinc;
711 for (i = 0; procs[i].name != NULL; i++) {
712 *result_code = 0;
713 ret = change_password_loop(context, creds, targprinc, newpw,
714 result_code, result_code_string,
715 result_string,
716 &procs[i]);
717 if (ret == 0 && *result_code == 0)
718 break;
721 if (targprinc == NULL)
722 krb5_free_principal(context, principal);
723 return ret;
730 krb5_error_code
731 krb5_set_password_using_ccache(krb5_context context,
732 krb5_ccache ccache,
733 char *newpw,
734 krb5_principal targprinc,
735 int *result_code,
736 krb5_data *result_code_string,
737 krb5_data *result_string)
739 krb5_creds creds, *credsp;
740 krb5_error_code ret;
741 krb5_principal principal = NULL;
743 *result_code = KRB5_KPASSWD_MALFORMED;
744 result_code_string->data = result_string->data = NULL;
745 result_code_string->length = result_string->length = 0;
747 memset(&creds, 0, sizeof(creds));
749 if (targprinc == NULL) {
750 ret = krb5_cc_get_principal(context, ccache, &principal);
751 if (ret)
752 return ret;
753 } else
754 principal = targprinc;
756 ret = krb5_make_principal(context, &creds.server,
757 krb5_principal_get_realm(context, principal),
758 "kadmin", "changepw", NULL);
759 if (ret)
760 goto out;
762 ret = krb5_cc_get_principal(context, ccache, &creds.client);
763 if (ret) {
764 krb5_free_principal(context, creds.server);
765 goto out;
768 ret = krb5_get_credentials(context, 0, ccache, &creds, &credsp);
769 krb5_free_principal(context, creds.server);
770 krb5_free_principal(context, creds.client);
771 if (ret)
772 goto out;
774 ret = krb5_set_password(context,
775 credsp,
776 newpw,
777 principal,
778 result_code,
779 result_code_string,
780 result_string);
782 krb5_free_creds(context, credsp);
784 return ret;
785 out:
786 if (targprinc == NULL)
787 krb5_free_principal(context, principal);
788 return ret;
795 const char*
796 krb5_passwd_result_to_string (krb5_context context,
797 int result)
799 static const char *strings[] = {
800 "Success",
801 "Malformed",
802 "Hard error",
803 "Auth error",
804 "Soft error" ,
805 "Access denied",
806 "Bad version",
807 "Initial flag needed"
810 if (result < 0 || result > KRB5_KPASSWD_INITIAL_FLAG_NEEDED)
811 return "unknown result code";
812 else
813 return strings[result];