No empty .Rs/.Re
[netbsd-mini2440.git] / crypto / dist / heimdal / lib / krb5 / changepw.c
blobda515411cf9d09d1bfd2eb9ea8b12f44fee84fce
1 /*
2 * Copyright (c) 1997 - 2005 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("$Heimdal: changepw.c 21505 2007-07-12 12:28:38Z lha $"
37 "$NetBSD$");
39 static void
40 str2data (krb5_data *d,
41 const char *fmt,
42 ...) __attribute__ ((format (printf, 2, 3)));
44 static void
45 str2data (krb5_data *d,
46 const char *fmt,
47 ...)
49 va_list args;
50 char *str;
52 va_start(args, fmt);
53 d->length = vasprintf (&str, fmt, args);
54 va_end(args);
55 d->data = str;
59 * Change password protocol defined by
60 * draft-ietf-cat-kerb-chg-password-02.txt
62 * Share the response part of the protocol with MS set password
63 * (RFC3244)
66 static krb5_error_code
67 chgpw_send_request (krb5_context context,
68 krb5_auth_context *auth_context,
69 krb5_creds *creds,
70 krb5_principal targprinc,
71 int is_stream,
72 int sock,
73 const char *passwd,
74 const char *host)
76 krb5_error_code ret;
77 krb5_data ap_req_data;
78 krb5_data krb_priv_data;
79 krb5_data passwd_data;
80 size_t len;
81 u_char header[6];
82 u_char *p;
83 struct iovec iov[3];
84 struct msghdr msghdr;
86 if (is_stream)
87 return KRB5_KPASSWD_MALFORMED;
89 if (targprinc &&
90 krb5_principal_compare(context, creds->client, targprinc) != TRUE)
91 return KRB5_KPASSWD_MALFORMED;
93 krb5_data_zero (&ap_req_data);
95 ret = krb5_mk_req_extended (context,
96 auth_context,
97 AP_OPTS_MUTUAL_REQUIRED | AP_OPTS_USE_SUBKEY,
98 NULL, /* in_data */
99 creds,
100 &ap_req_data);
101 if (ret)
102 return ret;
104 passwd_data.data = rk_UNCONST(passwd);
105 passwd_data.length = strlen(passwd);
107 krb5_data_zero (&krb_priv_data);
109 ret = krb5_mk_priv (context,
110 *auth_context,
111 &passwd_data,
112 &krb_priv_data,
113 NULL);
114 if (ret)
115 goto out2;
117 len = 6 + ap_req_data.length + krb_priv_data.length;
118 p = header;
119 *p++ = (len >> 8) & 0xFF;
120 *p++ = (len >> 0) & 0xFF;
121 *p++ = 0;
122 *p++ = 1;
123 *p++ = (ap_req_data.length >> 8) & 0xFF;
124 *p++ = (ap_req_data.length >> 0) & 0xFF;
126 memset(&msghdr, 0, sizeof(msghdr));
127 msghdr.msg_name = NULL;
128 msghdr.msg_namelen = 0;
129 msghdr.msg_iov = iov;
130 msghdr.msg_iovlen = sizeof(iov)/sizeof(*iov);
131 #if 0
132 msghdr.msg_control = NULL;
133 msghdr.msg_controllen = 0;
134 #endif
136 iov[0].iov_base = (void*)header;
137 iov[0].iov_len = 6;
138 iov[1].iov_base = ap_req_data.data;
139 iov[1].iov_len = ap_req_data.length;
140 iov[2].iov_base = krb_priv_data.data;
141 iov[2].iov_len = krb_priv_data.length;
143 if (sendmsg (sock, &msghdr, 0) < 0) {
144 ret = errno;
145 krb5_set_error_string(context, "sendmsg %s: %s", host, strerror(ret));
148 krb5_data_free (&krb_priv_data);
149 out2:
150 krb5_data_free (&ap_req_data);
151 return ret;
155 * Set password protocol as defined by RFC3244 --
156 * Microsoft Windows 2000 Kerberos Change Password and Set Password Protocols
159 static krb5_error_code
160 setpw_send_request (krb5_context context,
161 krb5_auth_context *auth_context,
162 krb5_creds *creds,
163 krb5_principal targprinc,
164 int is_stream,
165 int sock,
166 const char *passwd,
167 const char *host)
169 krb5_error_code ret;
170 krb5_data ap_req_data;
171 krb5_data krb_priv_data;
172 krb5_data pwd_data;
173 ChangePasswdDataMS chpw;
174 size_t len;
175 u_char header[4 + 6];
176 u_char *p;
177 struct iovec iov[3];
178 struct msghdr msghdr;
180 krb5_data_zero (&ap_req_data);
182 ret = krb5_mk_req_extended (context,
183 auth_context,
184 AP_OPTS_MUTUAL_REQUIRED | AP_OPTS_USE_SUBKEY,
185 NULL, /* in_data */
186 creds,
187 &ap_req_data);
188 if (ret)
189 return ret;
191 chpw.newpasswd.length = strlen(passwd);
192 chpw.newpasswd.data = rk_UNCONST(passwd);
193 if (targprinc) {
194 chpw.targname = &targprinc->name;
195 chpw.targrealm = &targprinc->realm;
196 } else {
197 chpw.targname = NULL;
198 chpw.targrealm = NULL;
201 ASN1_MALLOC_ENCODE(ChangePasswdDataMS, pwd_data.data, pwd_data.length,
202 &chpw, &len, ret);
203 if (ret) {
204 krb5_data_free (&ap_req_data);
205 return ret;
208 if(pwd_data.length != len)
209 krb5_abortx(context, "internal error in ASN.1 encoder");
211 ret = krb5_mk_priv (context,
212 *auth_context,
213 &pwd_data,
214 &krb_priv_data,
215 NULL);
216 if (ret)
217 goto out2;
219 len = 6 + ap_req_data.length + krb_priv_data.length;
220 p = header;
221 if (is_stream) {
222 _krb5_put_int(p, len, 4);
223 p += 4;
225 *p++ = (len >> 8) & 0xFF;
226 *p++ = (len >> 0) & 0xFF;
227 *p++ = 0xff;
228 *p++ = 0x80;
229 *p++ = (ap_req_data.length >> 8) & 0xFF;
230 *p++ = (ap_req_data.length >> 0) & 0xFF;
232 memset(&msghdr, 0, sizeof(msghdr));
233 msghdr.msg_name = NULL;
234 msghdr.msg_namelen = 0;
235 msghdr.msg_iov = iov;
236 msghdr.msg_iovlen = sizeof(iov)/sizeof(*iov);
237 #if 0
238 msghdr.msg_control = NULL;
239 msghdr.msg_controllen = 0;
240 #endif
242 iov[0].iov_base = (void*)header;
243 if (is_stream)
244 iov[0].iov_len = 10;
245 else
246 iov[0].iov_len = 6;
247 iov[1].iov_base = ap_req_data.data;
248 iov[1].iov_len = ap_req_data.length;
249 iov[2].iov_base = krb_priv_data.data;
250 iov[2].iov_len = krb_priv_data.length;
252 if (sendmsg (sock, &msghdr, 0) < 0) {
253 ret = errno;
254 krb5_set_error_string(context, "sendmsg %s: %s", host, strerror(ret));
257 krb5_data_free (&krb_priv_data);
258 out2:
259 krb5_data_free (&ap_req_data);
260 krb5_data_free (&pwd_data);
261 return ret;
264 static krb5_error_code
265 process_reply (krb5_context context,
266 krb5_auth_context auth_context,
267 int is_stream,
268 int sock,
269 int *result_code,
270 krb5_data *result_code_string,
271 krb5_data *result_string,
272 const char *host)
274 krb5_error_code ret;
275 u_char reply[1024 * 3];
276 ssize_t len;
277 uint16_t pkt_len, pkt_ver;
278 krb5_data ap_rep_data;
279 int save_errno;
281 len = 0;
282 if (is_stream) {
283 while (len < sizeof(reply)) {
284 unsigned long size;
286 ret = recvfrom (sock, reply + len, sizeof(reply) - len,
287 0, NULL, NULL);
288 if (ret < 0) {
289 save_errno = errno;
290 krb5_set_error_string(context, "recvfrom %s: %s",
291 host, strerror(save_errno));
292 return save_errno;
293 } else if (ret == 0) {
294 krb5_set_error_string(context, "recvfrom timeout %s", host);
295 return 1;
297 len += ret;
298 if (len < 4)
299 continue;
300 _krb5_get_int(reply, &size, 4);
301 if (size + 4 < len)
302 continue;
303 memmove(reply, reply + 4, size);
304 len = size;
305 break;
307 if (len == sizeof(reply)) {
308 krb5_set_error_string(context, "message too large from %s",
309 host);
310 return ENOMEM;
312 } else {
313 ret = recvfrom (sock, reply, sizeof(reply), 0, NULL, NULL);
314 if (ret < 0) {
315 save_errno = errno;
316 krb5_set_error_string(context, "recvfrom %s: %s",
317 host, strerror(save_errno));
318 return save_errno;
320 len = ret;
323 if (len < 6) {
324 str2data (result_string, "server %s sent to too short message "
325 "(%ld bytes)", host, (long)len);
326 *result_code = KRB5_KPASSWD_MALFORMED;
327 return 0;
330 pkt_len = (reply[0] << 8) | (reply[1]);
331 pkt_ver = (reply[2] << 8) | (reply[3]);
333 if ((pkt_len != len) || (reply[1] == 0x7e || reply[1] == 0x5e)) {
334 KRB_ERROR error;
335 size_t size;
336 u_char *p;
338 memset(&error, 0, sizeof(error));
340 ret = decode_KRB_ERROR(reply, len, &error, &size);
341 if (ret)
342 return ret;
344 if (error.e_data->length < 2) {
345 str2data(result_string, "server %s sent too short "
346 "e_data to print anything usable", host);
347 free_KRB_ERROR(&error);
348 *result_code = KRB5_KPASSWD_MALFORMED;
349 return 0;
352 p = error.e_data->data;
353 *result_code = (p[0] << 8) | p[1];
354 if (error.e_data->length == 2)
355 str2data(result_string, "server only sent error code");
356 else
357 krb5_data_copy (result_string,
358 p + 2,
359 error.e_data->length - 2);
360 free_KRB_ERROR(&error);
361 return 0;
364 if (pkt_len != len) {
365 str2data (result_string, "client: wrong len in reply");
366 *result_code = KRB5_KPASSWD_MALFORMED;
367 return 0;
369 if (pkt_ver != KRB5_KPASSWD_VERS_CHANGEPW) {
370 str2data (result_string,
371 "client: wrong version number (%d)", pkt_ver);
372 *result_code = KRB5_KPASSWD_MALFORMED;
373 return 0;
376 ap_rep_data.data = reply + 6;
377 ap_rep_data.length = (reply[4] << 8) | (reply[5]);
379 if (reply + len < (u_char *)ap_rep_data.data + ap_rep_data.length) {
380 str2data (result_string, "client: wrong AP len in reply");
381 *result_code = KRB5_KPASSWD_MALFORMED;
382 return 0;
385 if (ap_rep_data.length) {
386 krb5_ap_rep_enc_part *ap_rep;
387 krb5_data priv_data;
388 u_char *p;
390 priv_data.data = (u_char*)ap_rep_data.data + ap_rep_data.length;
391 priv_data.length = len - ap_rep_data.length - 6;
393 ret = krb5_rd_rep (context,
394 auth_context,
395 &ap_rep_data,
396 &ap_rep);
397 if (ret)
398 return ret;
400 krb5_free_ap_rep_enc_part (context, ap_rep);
402 ret = krb5_rd_priv (context,
403 auth_context,
404 &priv_data,
405 result_code_string,
406 NULL);
407 if (ret) {
408 krb5_data_free (result_code_string);
409 return ret;
412 if (result_code_string->length < 2) {
413 *result_code = KRB5_KPASSWD_MALFORMED;
414 str2data (result_string,
415 "client: bad length in result");
416 return 0;
419 p = result_code_string->data;
421 *result_code = (p[0] << 8) | p[1];
422 krb5_data_copy (result_string,
423 (unsigned char*)result_code_string->data + 2,
424 result_code_string->length - 2);
425 return 0;
426 } else {
427 KRB_ERROR error;
428 size_t size;
429 u_char *p;
431 ret = decode_KRB_ERROR(reply + 6, len - 6, &error, &size);
432 if (ret) {
433 return ret;
435 if (error.e_data->length < 2) {
436 krb5_warnx (context, "too short e_data to print anything usable");
437 return 1; /* XXX */
440 p = error.e_data->data;
441 *result_code = (p[0] << 8) | p[1];
442 krb5_data_copy (result_string,
443 p + 2,
444 error.e_data->length - 2);
445 return 0;
451 * change the password using the credentials in `creds' (for the
452 * principal indicated in them) to `newpw', storing the result of
453 * the operation in `result_*' and an error code or 0.
456 typedef krb5_error_code (*kpwd_send_request) (krb5_context,
457 krb5_auth_context *,
458 krb5_creds *,
459 krb5_principal,
460 int,
461 int,
462 const char *,
463 const char *);
464 typedef krb5_error_code (*kpwd_process_reply) (krb5_context,
465 krb5_auth_context,
466 int,
467 int,
468 int *,
469 krb5_data *,
470 krb5_data *,
471 const char *);
473 static struct kpwd_proc {
474 const char *name;
475 int flags;
476 #define SUPPORT_TCP 1
477 #define SUPPORT_UDP 2
478 kpwd_send_request send_req;
479 kpwd_process_reply process_rep;
480 } procs[] = {
482 "MS set password",
483 SUPPORT_TCP|SUPPORT_UDP,
484 setpw_send_request,
485 process_reply
488 "change password",
489 SUPPORT_UDP,
490 chgpw_send_request,
491 process_reply
493 { NULL }
496 static struct kpwd_proc *
497 find_chpw_proto(const char *name)
499 struct kpwd_proc *p;
500 for (p = procs; p->name != NULL; p++) {
501 if (strcmp(p->name, name) == 0)
502 return p;
504 return NULL;
511 static krb5_error_code
512 change_password_loop (krb5_context context,
513 krb5_creds *creds,
514 krb5_principal targprinc,
515 const char *newpw,
516 int *result_code,
517 krb5_data *result_code_string,
518 krb5_data *result_string,
519 struct kpwd_proc *proc)
521 krb5_error_code ret;
522 krb5_auth_context auth_context = NULL;
523 krb5_krbhst_handle handle = NULL;
524 krb5_krbhst_info *hi;
525 int sock;
526 int i;
527 int done = 0;
528 krb5_realm realm;
530 if (targprinc)
531 realm = targprinc->realm;
532 else
533 realm = creds->client->realm;
535 ret = krb5_auth_con_init (context, &auth_context);
536 if (ret)
537 return ret;
539 krb5_auth_con_setflags (context, auth_context,
540 KRB5_AUTH_CONTEXT_DO_SEQUENCE);
542 ret = krb5_krbhst_init (context, realm, KRB5_KRBHST_CHANGEPW, &handle);
543 if (ret)
544 goto out;
546 while (!done && (ret = krb5_krbhst_next(context, handle, &hi)) == 0) {
547 struct addrinfo *ai, *a;
548 int is_stream;
550 switch (hi->proto) {
551 case KRB5_KRBHST_UDP:
552 if ((proc->flags & SUPPORT_UDP) == 0)
553 continue;
554 is_stream = 0;
555 break;
556 case KRB5_KRBHST_TCP:
557 if ((proc->flags & SUPPORT_TCP) == 0)
558 continue;
559 is_stream = 1;
560 break;
561 default:
562 continue;
565 ret = krb5_krbhst_get_addrinfo(context, hi, &ai);
566 if (ret)
567 continue;
569 for (a = ai; !done && a != NULL; a = a->ai_next) {
570 int replied = 0;
572 sock = socket (a->ai_family, a->ai_socktype, a->ai_protocol);
573 if (sock < 0)
574 continue;
576 ret = connect(sock, a->ai_addr, a->ai_addrlen);
577 if (ret < 0) {
578 close (sock);
579 goto out;
582 ret = krb5_auth_con_genaddrs (context, auth_context, sock,
583 KRB5_AUTH_CONTEXT_GENERATE_LOCAL_ADDR);
584 if (ret) {
585 close (sock);
586 goto out;
589 for (i = 0; !done && i < 5; ++i) {
590 fd_set fdset;
591 struct timeval tv;
593 if (!replied) {
594 replied = 0;
596 ret = (*proc->send_req) (context,
597 &auth_context,
598 creds,
599 targprinc,
600 is_stream,
601 sock,
602 newpw,
603 hi->hostname);
604 if (ret) {
605 close(sock);
606 goto out;
610 if (sock >= FD_SETSIZE) {
611 krb5_set_error_string(context, "fd %d too large", sock);
612 ret = ERANGE;
613 close (sock);
614 goto out;
617 FD_ZERO(&fdset);
618 FD_SET(sock, &fdset);
619 tv.tv_usec = 0;
620 tv.tv_sec = 1 + (1 << i);
622 ret = select (sock + 1, &fdset, NULL, NULL, &tv);
623 if (ret < 0 && errno != EINTR) {
624 close(sock);
625 goto out;
627 if (ret == 1) {
628 ret = (*proc->process_rep) (context,
629 auth_context,
630 is_stream,
631 sock,
632 result_code,
633 result_code_string,
634 result_string,
635 hi->hostname);
636 if (ret == 0)
637 done = 1;
638 else if (i > 0 && ret == KRB5KRB_AP_ERR_MUT_FAIL)
639 replied = 1;
640 } else {
641 ret = KRB5_KDC_UNREACH;
644 close (sock);
648 out:
649 krb5_krbhst_free (context, handle);
650 krb5_auth_con_free (context, auth_context);
651 if (done)
652 return 0;
653 else {
654 if (ret == KRB5_KDC_UNREACH) {
655 krb5_set_error_string(context,
656 "unable to reach any changepw server "
657 " in realm %s", realm);
658 *result_code = KRB5_KPASSWD_HARDERROR;
660 return ret;
666 * change the password using the credentials in `creds' (for the
667 * principal indicated in them) to `newpw', storing the result of
668 * the operation in `result_*' and an error code or 0.
671 krb5_error_code KRB5_LIB_FUNCTION
672 krb5_change_password (krb5_context context,
673 krb5_creds *creds,
674 const char *newpw,
675 int *result_code,
676 krb5_data *result_code_string,
677 krb5_data *result_string)
679 struct kpwd_proc *p = find_chpw_proto("change password");
681 *result_code = KRB5_KPASSWD_MALFORMED;
682 result_code_string->data = result_string->data = NULL;
683 result_code_string->length = result_string->length = 0;
685 if (p == NULL)
686 return KRB5_KPASSWD_MALFORMED;
688 return change_password_loop(context, creds, NULL, newpw,
689 result_code, result_code_string,
690 result_string, p);
697 krb5_error_code KRB5_LIB_FUNCTION
698 krb5_set_password(krb5_context context,
699 krb5_creds *creds,
700 const char *newpw,
701 krb5_principal targprinc,
702 int *result_code,
703 krb5_data *result_code_string,
704 krb5_data *result_string)
706 krb5_principal principal = NULL;
707 krb5_error_code ret = 0;
708 int i;
710 *result_code = KRB5_KPASSWD_MALFORMED;
711 result_code_string->data = result_string->data = NULL;
712 result_code_string->length = result_string->length = 0;
714 if (targprinc == NULL) {
715 ret = krb5_get_default_principal(context, &principal);
716 if (ret)
717 return ret;
718 } else
719 principal = targprinc;
721 for (i = 0; procs[i].name != NULL; i++) {
722 *result_code = 0;
723 ret = change_password_loop(context, creds, principal, newpw,
724 result_code, result_code_string,
725 result_string,
726 &procs[i]);
727 if (ret == 0 && *result_code == 0)
728 break;
731 if (targprinc == NULL)
732 krb5_free_principal(context, principal);
733 return ret;
740 krb5_error_code KRB5_LIB_FUNCTION
741 krb5_set_password_using_ccache(krb5_context context,
742 krb5_ccache ccache,
743 const char *newpw,
744 krb5_principal targprinc,
745 int *result_code,
746 krb5_data *result_code_string,
747 krb5_data *result_string)
749 krb5_creds creds, *credsp;
750 krb5_error_code ret;
751 krb5_principal principal = NULL;
753 *result_code = KRB5_KPASSWD_MALFORMED;
754 result_code_string->data = result_string->data = NULL;
755 result_code_string->length = result_string->length = 0;
757 memset(&creds, 0, sizeof(creds));
759 if (targprinc == NULL) {
760 ret = krb5_cc_get_principal(context, ccache, &principal);
761 if (ret)
762 return ret;
763 } else
764 principal = targprinc;
766 ret = krb5_make_principal(context, &creds.server,
767 krb5_principal_get_realm(context, principal),
768 "kadmin", "changepw", NULL);
769 if (ret)
770 goto out;
772 ret = krb5_cc_get_principal(context, ccache, &creds.client);
773 if (ret) {
774 krb5_free_principal(context, creds.server);
775 goto out;
778 ret = krb5_get_credentials(context, 0, ccache, &creds, &credsp);
779 krb5_free_principal(context, creds.server);
780 krb5_free_principal(context, creds.client);
781 if (ret)
782 goto out;
784 ret = krb5_set_password(context,
785 credsp,
786 newpw,
787 principal,
788 result_code,
789 result_code_string,
790 result_string);
792 krb5_free_creds(context, credsp);
794 return ret;
795 out:
796 if (targprinc == NULL)
797 krb5_free_principal(context, principal);
798 return ret;
805 const char* KRB5_LIB_FUNCTION
806 krb5_passwd_result_to_string (krb5_context context,
807 int result)
809 static const char *strings[] = {
810 "Success",
811 "Malformed",
812 "Hard error",
813 "Auth error",
814 "Soft error" ,
815 "Access denied",
816 "Bad version",
817 "Initial flag needed"
820 if (result < 0 || result > KRB5_KPASSWD_INITIAL_FLAG_NEEDED)
821 return "unknown result code";
822 else
823 return strings[result];