1 /*
2 * Copyright 2010 Sun Microsystems, Inc. All rights reserved.
3 * Use is subject to license terms.
4 */
5
6 /*
7 * lib/krb5/kadm5/srv/chgpwd.c
8 *
9 * Copyright 1998 by the Massachusetts Institute of Technology.
10 * All Rights Reserved.
11 *
12 * Export of this software from the United States of America may
13 * require a specific license from the United States Government.
14 * It is the responsibility of any person or organization contemplating
15 * export to obtain such a license before exporting.
16 *
17 * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
18 * distribute this software and its documentation for any purpose and
19 * without fee is hereby granted, provided that the above copyright
20 * notice appear in all copies and that both that copyright notice and
21 * this permission notice appear in supporting documentation, and that
22 * the name of M.I.T. not be used in advertising or publicity pertaining
23 * to distribution of the software without specific, written prior
24 * permission. Furthermore if you modify this software you must label
25 * your software as modified software and not distribute it in such a
26 * fashion that it might be confused with the original M.I.T. software.
27 * M.I.T. makes no representations about the suitability of
28 * this software for any purpose. It is provided "as is" without express
29 * or implied warranty.
30 *
31 */
32
33 /*
34 * chgpwd.c - Handles changepw requests issued from non-Solaris krb5 clients.
35 */
36
37 #include <libintl.h>
38 #include <locale.h>
39 #include <kadm5/admin.h>
40 #include <syslog.h>
41 #include <krb5/adm_proto.h>
42
43 #define MAXAPREQ 1500
44
45 static krb5_error_code
process_chpw_request(krb5_context context,void * server_handle,char * realm,int s,krb5_keytab keytab,struct sockaddr_in * sin,krb5_data * req,krb5_data * rep)46 process_chpw_request(krb5_context context, void *server_handle,
47 char *realm, int s, krb5_keytab keytab,
48 struct sockaddr_in *sin, krb5_data *req,
49 krb5_data *rep)
50 {
51 krb5_error_code ret;
52 char *ptr;
53 int plen, vno;
54 krb5_address local_kaddr, remote_kaddr;
55 int allocated_mem = 0;
56 krb5_data ap_req, ap_rep;
57 krb5_auth_context auth_context;
58 krb5_principal changepw;
59 krb5_ticket *ticket;
60 krb5_data cipher, clear;
61 struct sockaddr local_addr, remote_addr;
62 int addrlen;
63 krb5_replay_data replay;
64 krb5_error krberror;
65 int numresult;
66 char strresult[1024];
67 char *clientstr;
68 size_t clen;
69 char *cdots;
70
71 ret = 0;
72 rep->length = 0;
73
74 auth_context = NULL;
75 changepw = NULL;
76 ap_rep.length = 0;
77 ap_rep.data = NULL;
78 ticket = NULL;
79 clear.length = 0;
80 clear.data = NULL;
81 cipher.length = 0;
82 cipher.data = NULL;
83
84 if (req->length < 4) {
85 /*
86 * either this, or the server is printing bad messages,
87 * or the caller passed in garbage
88 */
89 ret = KRB5KRB_AP_ERR_MODIFIED;
90 numresult = KRB5_KPASSWD_MALFORMED;
91 (void) strlcpy(strresult, "Request was truncated",
92 sizeof (strresult));
93 goto chpwfail;
94 }
95
96 ptr = req->data;
97
98 /*
99 * Verify length
100 */
101 plen = (*ptr++ & 0xff);
102 plen = (plen<<8) | (*ptr++ & 0xff);
103
104 if (plen != req->length)
105 return (KRB5KRB_AP_ERR_MODIFIED);
106
107 /*
108 * Verify version number
109 */
110 vno = (*ptr++ & 0xff);
111 vno = (vno<<8) | (*ptr++ & 0xff);
112
113 if (vno != 1) {
114 ret = KRB5KDC_ERR_BAD_PVNO;
115 numresult = KRB5_KPASSWD_MALFORMED;
116 (void) snprintf(strresult, sizeof (strresult),
117 "Request contained unknown protocol version number %d",
118 vno);
119 goto chpwfail;
120 }
121
122 /*
123 * Read, check ap-req length
124 */
125 ap_req.length = (*ptr++ & 0xff);
126 ap_req.length = (ap_req.length<<8) | (*ptr++ & 0xff);
127
128 if (ptr + ap_req.length >= req->data + req->length) {
129 ret = KRB5KRB_AP_ERR_MODIFIED;
130 numresult = KRB5_KPASSWD_MALFORMED;
131 (void) strlcpy(strresult, "Request was truncated in AP-REQ",
132 sizeof (strresult));
133 goto chpwfail;
134 }
135
136 /*
137 * Verify ap_req
138 */
139 ap_req.data = ptr;
140 ptr += ap_req.length;
141
142 if (ret = krb5_auth_con_init(context, &auth_context)) {
143 krb5_klog_syslog(LOG_ERR,
144 gettext("Change password request failed. "
145 "Failed initializing auth context: %s"),
146 error_message(ret));
147 numresult = KRB5_KPASSWD_HARDERROR;
148 (void) strlcpy(strresult, "Failed initializing auth context",
149 sizeof (strresult));
150 goto chpwfail;
151 }
152
153 if (ret = krb5_auth_con_setflags(context, auth_context,
154 KRB5_AUTH_CONTEXT_DO_SEQUENCE)) {
155 krb5_klog_syslog(LOG_ERR,
156 gettext("Change password request failed. "
157 "Failed setting auth "
158 "context flags: %s"),
159 error_message(ret));
160 numresult = KRB5_KPASSWD_HARDERROR;
161 (void) strlcpy(strresult, "Failed initializing auth context",
162 sizeof (strresult));
163 goto chpwfail;
164 }
165
166 if (ret = krb5_build_principal(context, &changepw, strlen(realm), realm,
167 "kadmin", "changepw", NULL)) {
168 krb5_klog_syslog(LOG_ERR,
169 gettext("Change password request failed "
170 "Failed to build kadmin/changepw "
171 "principal: %s"),
172 error_message(ret));
173 numresult = KRB5_KPASSWD_HARDERROR;
174 (void) strlcpy(strresult,
175 "Failed building kadmin/changepw principal",
176 sizeof (strresult));
177 goto chpwfail;
178 }
179
180 ret = krb5_rd_req(context, &auth_context, &ap_req, changepw, keytab,
181 NULL, &ticket);
182
183 if (ret) {
184 char kt_name[MAX_KEYTAB_NAME_LEN];
185 if (krb5_kt_get_name(context, keytab,
186 kt_name, sizeof (kt_name)))
187 strncpy(kt_name, "default keytab", sizeof (kt_name));
188
189 switch (ret) {
190 case KRB5_KT_NOTFOUND:
191 krb5_klog_syslog(LOG_ERR,
192 gettext("Change password request failed because "
193 "keytab entry \"kadmin/changepw\" "
194 "is missing from \"%s\""),
195 kt_name);
196 break;
197 case ENOENT:
198 krb5_klog_syslog(LOG_ERR,
199 gettext("Change password request failed because "
200 "keytab file \"%s\" does not exist"),
201 kt_name);
202 break;
203 default:
204 krb5_klog_syslog(LOG_ERR,
205 gettext("Change password request failed. "
206 "Failed to parse Kerberos AP_REQ message: %s"),
207 error_message(ret));
208 }
209
210 numresult = KRB5_KPASSWD_AUTHERROR;
211 (void) strlcpy(strresult, "Failed reading application request",
212 sizeof (strresult));
213 goto chpwfail;
214 }
215
216 /*
217 * Set up address info
218 */
219 addrlen = sizeof (local_addr);
220
221 if (getsockname(s, &local_addr, &addrlen) < 0) {
222 ret = errno;
223 numresult = KRB5_KPASSWD_HARDERROR;
224 (void) strlcpy(strresult,
225 "Failed getting server internet address",
226 sizeof (strresult));
227 goto chpwfail;
228 }
229
230 /*
231 * Some brain-dead OS's don't return useful information from
232 * the getsockname call. Namely, windows and solaris.
233 */
234 if (((struct sockaddr_in *)&local_addr)->sin_addr.s_addr != 0) {
235 local_kaddr.addrtype = ADDRTYPE_INET;
236 local_kaddr.length = sizeof (((struct sockaddr_in *)
237 &local_addr)->sin_addr);
238 /* CSTYLED */
239 local_kaddr.contents = (krb5_octet *) &(((struct sockaddr_in *)&local_addr)->sin_addr);
240 } else {
241 krb5_address **addrs;
242
243 krb5_os_localaddr(context, &addrs);
244
245 local_kaddr.magic = addrs[0]->magic;
246 local_kaddr.addrtype = addrs[0]->addrtype;
247 local_kaddr.length = addrs[0]->length;
248 if ((local_kaddr.contents = malloc(addrs[0]->length)) == 0) {
249 ret = errno;
250 numresult = KRB5_KPASSWD_HARDERROR;
251 (void) strlcpy(strresult,
252 "Malloc failed for local_kaddr",
253 sizeof (strresult));
254 goto chpwfail;
255 }
256
257 (void) memcpy(local_kaddr.contents, addrs[0]->contents,
258 addrs[0]->length);
259 allocated_mem++;
260
261 krb5_free_addresses(context, addrs);
262 }
263
264 addrlen = sizeof (remote_addr);
265
266 if (getpeername(s, &remote_addr, &addrlen) < 0) {
267 ret = errno;
268 numresult = KRB5_KPASSWD_HARDERROR;
269 (void) strlcpy(strresult,
270 "Failed getting client internet address",
271 sizeof (strresult));
272 goto chpwfail;
273 }
274
275 remote_kaddr.addrtype = ADDRTYPE_INET;
276 remote_kaddr.length = sizeof (((struct sockaddr_in *)
277 &remote_addr)->sin_addr);
278 /* CSTYLED */
279 remote_kaddr.contents = (krb5_octet *) &(((struct sockaddr_in *)&remote_addr)->sin_addr);
280 remote_kaddr.addrtype = ADDRTYPE_INET;
281 remote_kaddr.length = sizeof (sin->sin_addr);
282 remote_kaddr.contents = (krb5_octet *) &sin->sin_addr;
283
284 /*
285 * mk_priv requires that the local address be set.
286 * getsockname is used for this. rd_priv requires that the
287 * remote address be set. recvfrom is used for this. If
288 * rd_priv is given a local address, and the message has the
289 * recipient addr in it, this will be checked. However, there
290 * is simply no way to know ahead of time what address the
291 * message will be delivered *to*. Therefore, it is important
292 * that either no recipient address is in the messages when
293 * mk_priv is called, or that no local address is passed to
294 * rd_priv. Both is a better idea, and I have done that. In
295 * summary, when mk_priv is called, *only* a local address is
296 * specified. when rd_priv is called, *only* a remote address
297 * is specified. Are we having fun yet?
298 */
299 if (ret = krb5_auth_con_setaddrs(context, auth_context, NULL,
300 &remote_kaddr)) {
301 numresult = KRB5_KPASSWD_HARDERROR;
302 (void) strlcpy(strresult,
303 "Failed storing client internet address",
304 sizeof (strresult));
305 goto chpwfail;
306 }
307
308 /*
309 * Verify that this is an AS_REQ ticket
310 */
311 if (!(ticket->enc_part2->flags & TKT_FLG_INITIAL)) {
312 numresult = KRB5_KPASSWD_AUTHERROR;
313 (void) strlcpy(strresult,
314 "Ticket must be derived from a password",
315 sizeof (strresult));
316 goto chpwfail;
317 }
318
319 /*
320 * Construct the ap-rep
321 */
322 if (ret = krb5_mk_rep(context, auth_context, &ap_rep)) {
323 numresult = KRB5_KPASSWD_AUTHERROR;
324 (void) strlcpy(strresult,
325 "Failed replying to application request",
326 sizeof (strresult));
327 goto chpwfail;
328 }
329
330 /*
331 * Decrypt the new password
332 */
333 cipher.length = (req->data + req->length) - ptr;
334 cipher.data = ptr;
335
336 if (ret = krb5_rd_priv(context, auth_context, &cipher,
337 &clear, &replay)) {
338 numresult = KRB5_KPASSWD_HARDERROR;
339 (void) strlcpy(strresult, "Failed decrypting request",
340 sizeof (strresult));
341 goto chpwfail;
342 }
343
344 ret = krb5_unparse_name(context, ticket->enc_part2->client, &clientstr);
345 if (ret) {
346 numresult = KRB5_KPASSWD_HARDERROR;
347 (void) strcpy(strresult, "Failed decrypting request");
348 goto chpwfail;
349 }
350
351 /*
352 * Change the password
353 */
354 if ((ptr = (char *)malloc(clear.length + 1)) == NULL) {
355 ret = errno;
356 numresult = KRB5_KPASSWD_HARDERROR;
357 (void) strlcpy(strresult, "Malloc failed for ptr",
358 sizeof (strresult));
359 krb5_free_unparsed_name(context, clientstr);
360 goto chpwfail;
361 }
362 (void) memcpy(ptr, clear.data, clear.length);
363 ptr[clear.length] = '\0';
364
365 ret = (kadm5_ret_t)kadm5_chpass_principal_util(server_handle,
366 ticket->enc_part2->client,
367 ptr, NULL, strresult,
368 sizeof (strresult));
369
370 /*
371 * Zap the password
372 */
373 (void) memset(clear.data, 0, clear.length);
374 (void) memset(ptr, 0, clear.length);
375 if (clear.data != NULL) {
376 krb5_xfree(clear.data);
377 clear.data = NULL;
378 }
379 free(ptr);
380 clear.length = 0;
381
382 clen = strlen(clientstr);
383 trunc_name(&clen, &cdots);
384 krb5_klog_syslog(LOG_NOTICE, "chpw request from %s for %.*s%s: %s",
385 inet_ntoa(((struct sockaddr_in *)&remote_addr)->sin_addr),
386 clen, clientstr, cdots, ret ? error_message(ret) : "success");
387 krb5_free_unparsed_name(context, clientstr);
388
389 if (ret) {
390 if ((ret != KADM5_PASS_Q_TOOSHORT) &&
391 (ret != KADM5_PASS_REUSE) &&
392 (ret != KADM5_PASS_Q_CLASS) &&
393 (ret != KADM5_PASS_Q_DICT) &&
394 (ret != KADM5_PASS_TOOSOON))
395 numresult = KRB5_KPASSWD_HARDERROR;
396 else
397 numresult = KRB5_KPASSWD_SOFTERROR;
398 /*
399 * strresult set by kadb5_chpass_principal_util()
400 */
401 goto chpwfail;
402 }
403
404 /*
405 * Success!
406 */
407 numresult = KRB5_KPASSWD_SUCCESS;
408 (void) strlcpy(strresult, "", sizeof (strresult));
409
410 chpwfail:
411
412 clear.length = 2 + strlen(strresult);
413 if (clear.data != NULL) {
414 krb5_xfree(clear.data);
415 clear.data = NULL;
416 }
417 if ((clear.data = (char *)malloc(clear.length)) == NULL) {
418 ret = errno;
419 numresult = KRB5_KPASSWD_HARDERROR;
420 (void) strlcpy(strresult, "Malloc failed for clear.data",
421 sizeof (strresult));
422 }
423
424 ptr = clear.data;
425
426 *ptr++ = (numresult>>8) & 0xff;
427 *ptr++ = numresult & 0xff;
428
429 (void) memcpy(ptr, strresult, strlen(strresult));
430
431 cipher.length = 0;
432
433 if (ap_rep.length) {
434 if (ret = krb5_auth_con_setaddrs(context, auth_context,
435 &local_kaddr, NULL)) {
436 numresult = KRB5_KPASSWD_HARDERROR;
437 (void) strlcpy(strresult,
438 "Failed storing client and server internet addresses",
439 sizeof (strresult));
440 } else {
441 if (ret = krb5_mk_priv(context, auth_context, &clear,
442 &cipher, &replay)) {
443 numresult = KRB5_KPASSWD_HARDERROR;
444 (void) strlcpy(strresult,
445 "Failed encrypting reply",
446 sizeof (strresult));
447 }
448 }
449 }
450
451 /*
452 * If no KRB-PRIV was constructed, then we need a KRB-ERROR.
453 * If this fails, just bail. There's nothing else we can do.
454 */
455 if (cipher.length == 0) {
456 /*
457 * Clear out ap_rep now, so that it won't be inserted
458 * in the reply
459 */
460 if (ap_rep.length) {
461 if (ap_rep.data != NULL)
462 krb5_xfree(ap_rep.data);
463 ap_rep.data = NULL;
464 ap_rep.length = 0;
465 }
466
467 krberror.ctime = 0;
468 krberror.cusec = 0;
469 krberror.susec = 0;
470 if (ret = krb5_timeofday(context, &krberror.stime))
471 goto bailout;
472
473 /*
474 * This is really icky. but it's what all the other callers
475 * to mk_error do.
476 */
477 krberror.error = ret;
478 krberror.error -= ERROR_TABLE_BASE_krb5;
479 if (krberror.error < 0 || krberror.error > 128)
480 krberror.error = KRB_ERR_GENERIC;
481
482 krberror.client = NULL;
483 if (ret = krb5_build_principal(context, &krberror.server,
484 strlen(realm), realm,
485 "kadmin", "changepw", NULL)) {
486 goto bailout;
487 }
488
489 krberror.text.length = 0;
490 krberror.e_data = clear;
491
492 ret = krb5_mk_error(context, &krberror, &cipher);
493
494 krb5_free_principal(context, krberror.server);
495
496 if (ret)
497 goto bailout;
498 }
499
500 /*
501 * Construct the reply
502 */
503 rep->length = 6 + ap_rep.length + cipher.length;
504 if ((rep->data = (char *)malloc(rep->length)) == NULL) {
505 ret = errno;
506 goto bailout;
507 }
508 ptr = rep->data;
509
510 /*
511 * Length
512 */
513 *ptr++ = (rep->length>>8) & 0xff;
514 *ptr++ = rep->length & 0xff;
515
516 /*
517 * Version == 0x0001 big-endian
518 */
519 *ptr++ = 0;
520 *ptr++ = 1;
521
522 /*
523 * ap_rep length, big-endian
524 */
525 *ptr++ = (ap_rep.length>>8) & 0xff;
526 *ptr++ = ap_rep.length & 0xff;
527
528 /*
529 * ap-rep data
530 */
531 if (ap_rep.length) {
532 (void) memcpy(ptr, ap_rep.data, ap_rep.length);
533 ptr += ap_rep.length;
534 }
535
536 /*
537 * krb-priv or krb-error
538 */
539 (void) memcpy(ptr, cipher.data, cipher.length);
540
541 bailout:
542 if (auth_context)
543 krb5_auth_con_free(context, auth_context);
544 if (changepw)
545 krb5_free_principal(context, changepw);
546 if (ap_rep.data != NULL)
547 krb5_xfree(ap_rep.data);
548 if (ticket)
549 krb5_free_ticket(context, ticket);
550 if (clear.data != NULL)
551 krb5_xfree(clear.data);
552 if (cipher.data != NULL)
553 krb5_xfree(cipher.data);
554 if (allocated_mem)
555 krb5_xfree(local_kaddr.contents);
556
557 return (ret);
558 }
559
560
561 /*
562 * This routine is used to handle password-change requests received
563 * on kpasswd-port 464 from MIT/M$ clients.
564 */
565 void
handle_chpw(krb5_context context,int s1,void * serverhandle,kadm5_config_params * params)566 handle_chpw(krb5_context context, int s1,
567 void *serverhandle, kadm5_config_params *params)
568 {
569 krb5_error_code ret;
570 char req[MAXAPREQ];
571 int len;
572 struct sockaddr_in from;
573 int fromlen;
574 krb5_keytab kt;
575 krb5_data reqdata, repdata;
576 int s2 = -1;
577
578 reqdata.length = 0;
579 reqdata.data = NULL;
580 repdata.length = 0;
581 repdata.data = NULL;
582
583 fromlen = sizeof (from);
584
585 if ((len = recvfrom(s1, req, sizeof (req), 0, (struct sockaddr *)&from,
586 &fromlen)) < 0) {
587 krb5_klog_syslog(LOG_ERR, gettext("chpw: Couldn't receive "
588 "request: %s"), error_message(errno));
589 return;
590 }
591
592 /*
593 * Solaris Kerberos:
594 * The only caller is kadmind, which is the master and therefore has the
595 * correct keys in the KDB, rather than obtaining them via the
596 * kadm5.keytab, by default.
597 */
598 if ((ret = krb5_kt_resolve(context, "KDB:", &kt))) {
599 krb5_klog_syslog(LOG_ERR, gettext("chpw: Couldn't open "
600 "admin keytab %s"), error_message(ret));
601 return;
602 }
603
604 reqdata.length = len;
605 reqdata.data = req;
606
607 /*
608 * This is really obscure. s1 is used for all communications. it
609 * is left unconnected in case the server is multihomed and routes
610 * are asymmetric. s2 is connected to resolve routes and get
611 * addresses. this is the *only* way to get proper addresses for
612 * multihomed hosts if routing is asymmetric.
613 *
614 * A related problem in the server, but not the client, is that
615 * many os's have no way to disconnect a connected udp socket, so
616 * the s2 socket needs to be closed and recreated for each
617 * request. The s1 socket must not be closed, or else queued
618 * requests will be lost.
619 *
620 * A "naive" client implementation (one socket, no connect,
621 * hostname resolution to get the local ip addr) will work and
622 * interoperate if the client is single-homed.
623 */
624
625 if ((s2 = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
626 krb5_klog_syslog(LOG_ERR, gettext("chpw: Cannot create "
627 "connecting socket: %s"), error_message(errno));
628 goto cleanup;
629 }
630
631 if (connect(s2, (struct sockaddr *)&from, sizeof (from)) < 0) {
632 krb5_klog_syslog(LOG_ERR, gettext("chpw: Couldn't connect "
633 "to client: %s"), error_message(errno));
634 if (s2 > 0)
635 (void) close(s2);
636 goto cleanup;
637 }
638
639 if ((ret = process_chpw_request(context, serverhandle,
640 params->realm, s2, kt, &from,
641 &reqdata, &repdata))) {
642 krb5_klog_syslog(LOG_ERR, gettext("chpw: Error processing "
643 "request: %s"), error_message(ret));
644 }
645
646 if (s2 > 0)
647 (void) close(s2);
648
649 if (repdata.length == 0 || repdata.data == NULL) {
650 /*
651 * Just return. This means something really bad happened
652 */
653 goto cleanup;
654 }
655
656 len = sendto(s1, repdata.data, repdata.length, 0,
657 (struct sockaddr *)&from, sizeof (from));
658
659 if (len < repdata.length) {
660 krb5_xfree(repdata.data);
661
662 krb5_klog_syslog(LOG_ERR, gettext("chpw: Error sending reply:"
663 " %s"), error_message(errno));
664 goto cleanup;
665 }
666
667 if (repdata.data != NULL)
668 krb5_xfree(repdata.data);
669 cleanup:
670 krb5_kt_close(context, kt);
671 }
672