xref: /illumos-gate/usr/src/lib/krb5/kadm5/srv/chgpwd.c (revision 2278144afd2005b3eabcfd9bc412fe5ceb78749d)
1 /*
2  * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
3  * Use is subject to license terms.
4  */
5 
6 #pragma ident	"%Z%%M%	%I%	%E% SMI"
7 
8 /*
9  * lib/krb5/kadm5/srv/chgpwd.c
10  *
11  * Copyright 1998 by the Massachusetts Institute of Technology.
12  * All Rights Reserved.
13  *
14  * Export of this software from the United States of America may
15  *   require a specific license from the United States Government.
16  *   It is the responsibility of any person or organization contemplating
17  *   export to obtain such a license before exporting.
18  *
19  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
20  * distribute this software and its documentation for any purpose and
21  * without fee is hereby granted, provided that the above copyright
22  * notice appear in all copies and that both that copyright notice and
23  * this permission notice appear in supporting documentation, and that
24  * the name of M.I.T. not be used in advertising or publicity pertaining
25  * to distribution of the software without specific, written prior
26  * permission.  Furthermore if you modify this software you must label
27  * your software as modified software and not distribute it in such a
28  * fashion that it might be confused with the original M.I.T. software.
29  * M.I.T. makes no representations about the suitability of
30  * this software for any purpose.  It is provided "as is" without express
31  * or implied warranty.
32  *
33  */
34 
35 /*
36  * chgpwd.c - Handles changepw requests issued from non-Solaris krb5 clients.
37  */
38 
39 #include <libintl.h>
40 #include <locale.h>
41 #include <kadm5/admin.h>
42 #include <syslog.h>
43 #include <krb5/adm_proto.h>
44 
45 #define	MAXAPREQ 1500
46 
47 static krb5_error_code
48 process_chpw_request(krb5_context context, void *server_handle,
49 			char *realm, int s, krb5_keytab keytab,
50 			struct sockaddr_in *sin, krb5_data *req,
51 			krb5_data *rep)
52 {
53 	krb5_error_code ret;
54 	char *ptr;
55 	int plen, vno;
56 	krb5_address local_kaddr, remote_kaddr;
57 	int allocated_mem = 0;
58 	krb5_data ap_req, ap_rep;
59 	krb5_auth_context auth_context;
60 	krb5_principal changepw;
61 	krb5_ticket *ticket;
62 	krb5_data cipher, clear;
63 	struct sockaddr local_addr, remote_addr;
64 	int addrlen;
65 	krb5_replay_data replay;
66 	krb5_error krberror;
67 	int numresult;
68 	char strresult[1024];
69 	char *clientstr;
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 	krb5_klog_syslog(LOG_NOTICE, "chpw request from %s for %s: %s",
383 		inet_ntoa(((struct sockaddr_in *)&remote_addr)->sin_addr),
384 		clientstr, ret ? error_message(ret) : "success");
385 	krb5_free_unparsed_name(context, clientstr);
386 
387 	if (ret) {
388 		if ((ret != KADM5_PASS_Q_TOOSHORT) &&
389 		    (ret != KADM5_PASS_REUSE) &&
390 		    (ret != KADM5_PASS_Q_CLASS) &&
391 		    (ret != KADM5_PASS_Q_DICT) &&
392 		    (ret != KADM5_PASS_TOOSOON))
393 			numresult = KRB5_KPASSWD_HARDERROR;
394 		else
395 			numresult = KRB5_KPASSWD_SOFTERROR;
396 		/*
397 		 * strresult set by kadb5_chpass_principal_util()
398 		 */
399 		goto chpwfail;
400 	}
401 
402 	/*
403 	 * Success!
404 	 */
405 	numresult = KRB5_KPASSWD_SUCCESS;
406 	(void) strlcpy(strresult, "", sizeof (strresult));
407 
408 chpwfail:
409 
410 	clear.length = 2 + strlen(strresult);
411 	if (clear.data != NULL) {
412 		krb5_xfree(clear.data);
413 		clear.data = NULL;
414 	}
415 	if ((clear.data = (char *)malloc(clear.length)) == NULL) {
416 		ret = errno;
417 		numresult = KRB5_KPASSWD_HARDERROR;
418 		(void) strlcpy(strresult, "Malloc failed for clear.data",
419 			sizeof (strresult));
420 	}
421 
422 	ptr = clear.data;
423 
424 	*ptr++ = (numresult>>8) & 0xff;
425 	*ptr++ = numresult & 0xff;
426 
427 	(void) memcpy(ptr, strresult, strlen(strresult));
428 
429 	cipher.length = 0;
430 
431 	if (ap_rep.length) {
432 		if (ret = krb5_auth_con_setaddrs(context, auth_context,
433 					&local_kaddr, NULL)) {
434 		    numresult = KRB5_KPASSWD_HARDERROR;
435 		    (void) strlcpy(strresult,
436 			"Failed storing client and server internet addresses",
437 			sizeof (strresult));
438 		} else {
439 			if (ret = krb5_mk_priv(context, auth_context, &clear,
440 						&cipher, &replay)) {
441 				numresult = KRB5_KPASSWD_HARDERROR;
442 				(void) strlcpy(strresult,
443 					"Failed encrypting reply",
444 					sizeof (strresult));
445 			}
446 		}
447 	}
448 
449 	/*
450 	 * If no KRB-PRIV was constructed, then we need a KRB-ERROR.
451 	 * If this fails, just bail.  There's nothing else we can do.
452 	 */
453 	if (cipher.length == 0) {
454 		/*
455 		 * Clear out ap_rep now, so that it won't be inserted
456 		 * in the reply
457 		 */
458 		if (ap_rep.length) {
459 			if (ap_rep.data != NULL)
460 				krb5_xfree(ap_rep.data);
461 			ap_rep.data = NULL;
462 			ap_rep.length = 0;
463 		}
464 
465 		krberror.ctime = 0;
466 		krberror.cusec = 0;
467 		krberror.susec = 0;
468 		if (ret = krb5_timeofday(context, &krberror.stime))
469 			goto bailout;
470 
471 		/*
472 		 * This is really icky.  but it's what all the other callers
473 		 * to mk_error do.
474 		 */
475 		krberror.error = ret;
476 		krberror.error -= ERROR_TABLE_BASE_krb5;
477 		if (krberror.error < 0 || krberror.error > 128)
478 			krberror.error = KRB_ERR_GENERIC;
479 
480 		krberror.client = NULL;
481 		if (ret = krb5_build_principal(context, &krberror.server,
482 					    strlen(realm), realm,
483 					    "kadmin", "changepw", NULL)) {
484 			goto bailout;
485 		}
486 
487 		krberror.text.length = 0;
488 		krberror.e_data = clear;
489 
490 		ret = krb5_mk_error(context, &krberror, &cipher);
491 
492 		krb5_free_principal(context, krberror.server);
493 
494 		if (ret)
495 			goto bailout;
496 	}
497 
498 	/*
499 	 * Construct the reply
500 	 */
501 	rep->length = 6 + ap_rep.length + cipher.length;
502 	if ((rep->data = (char *)malloc(rep->length)) == NULL)  {
503 		ret = errno;
504 		goto bailout;
505 	}
506 	ptr = rep->data;
507 
508 	/*
509 	 * Length
510 	 */
511 	*ptr++ = (rep->length>>8) & 0xff;
512 	*ptr++ = rep->length & 0xff;
513 
514 	/*
515 	 * Version == 0x0001 big-endian
516 	 */
517 	*ptr++ = 0;
518 	*ptr++ = 1;
519 
520 	/*
521 	 * ap_rep length, big-endian
522 	 */
523 	*ptr++ = (ap_rep.length>>8) & 0xff;
524 	*ptr++ = ap_rep.length & 0xff;
525 
526 	/*
527 	 * ap-rep data
528 	 */
529 	if (ap_rep.length) {
530 		(void) memcpy(ptr, ap_rep.data, ap_rep.length);
531 		ptr += ap_rep.length;
532 	}
533 
534 	/*
535 	 * krb-priv or krb-error
536 	 */
537 	(void) memcpy(ptr, cipher.data, cipher.length);
538 
539 bailout:
540 	if (auth_context)
541 		krb5_auth_con_free(context, auth_context);
542 	if (changepw)
543 		krb5_free_principal(context, changepw);
544 	if (ap_rep.data != NULL)
545 		krb5_xfree(ap_rep.data);
546 	if (ticket)
547 		krb5_free_ticket(context, ticket);
548 	if (clear.data != NULL)
549 		krb5_xfree(clear.data);
550 	if (cipher.data != NULL)
551 		krb5_xfree(cipher.data);
552 	if (allocated_mem)
553 		krb5_xfree(local_kaddr.contents);
554 
555 	return (ret);
556 }
557 
558 
559 /*
560  * This routine is used to handle password-change requests received
561  * on kpasswd-port 464 from MIT/M$ clients.
562  */
563 void
564 handle_chpw(krb5_context context, int s1,
565 		void *serverhandle, kadm5_config_params *params)
566 {
567 	krb5_error_code ret;
568 	char req[MAXAPREQ];
569 	int len;
570 	struct sockaddr_in from;
571 	int fromlen;
572 	krb5_keytab kt;
573 	krb5_data reqdata, repdata;
574 	int s2 = -1;
575 
576 	reqdata.length = 0;
577 	reqdata.data = NULL;
578 	repdata.length = 0;
579 	repdata.data = NULL;
580 
581 	fromlen = sizeof (from);
582 
583 	if ((len = recvfrom(s1, req, sizeof (req), 0, (struct sockaddr *)&from,
584 			    &fromlen)) < 0) {
585 		krb5_klog_syslog(LOG_ERR, gettext("chpw: Couldn't receive "
586 				"request: %s"), error_message(errno));
587 		return;
588 	}
589 
590 	if ((ret = krb5_kt_resolve(context, params->admin_keytab, &kt))) {
591 		krb5_klog_syslog(LOG_ERR, gettext("chpw: Couldn't open "
592 				"admin keytab %s"), error_message(ret));
593 		return;
594 	}
595 
596 	reqdata.length = len;
597 	reqdata.data = req;
598 
599 	/*
600 	 * This is really obscure.  s1 is used for all communications.  it
601 	 * is left unconnected in case the server is multihomed and routes
602 	 * are asymmetric.  s2 is connected to resolve routes and get
603 	 * addresses.  this is the *only* way to get proper addresses for
604 	 * multihomed hosts if routing is asymmetric.
605 	 *
606 	 * A related problem in the server, but not the client, is that
607 	 * many os's have no way to disconnect a connected udp socket, so
608 	 * the s2 socket needs to be closed and recreated for each
609 	 * request.  The s1 socket must not be closed, or else queued
610 	 * requests will be lost.
611 	 *
612 	 * A "naive" client implementation (one socket, no connect,
613 	 * hostname resolution to get the local ip addr) will work and
614 	 * interoperate if the client is single-homed.
615 	 */
616 
617 	if ((s2 = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
618 		krb5_klog_syslog(LOG_ERR, gettext("chpw: Cannot create "
619 				"connecting socket: %s"), error_message(errno));
620 		goto cleanup;
621 	}
622 
623 	if (connect(s2, (struct sockaddr *)&from, sizeof (from)) < 0) {
624 		krb5_klog_syslog(LOG_ERR, gettext("chpw: Couldn't connect "
625 				"to client: %s"), error_message(errno));
626 		if (s2 > 0)
627 			(void) close(s2);
628 		goto cleanup;
629 	}
630 
631 	if ((ret = process_chpw_request(context, serverhandle,
632 					params->realm, s2, kt, &from,
633 					&reqdata, &repdata))) {
634 		krb5_klog_syslog(LOG_ERR, gettext("chpw: Error processing "
635 				"request: %s"), error_message(ret));
636 	}
637 
638 	if (s2 > 0)
639 		(void) close(s2);
640 
641 	if (repdata.length == 0 || repdata.data == NULL) {
642 		/*
643 		 * Just return.  This means something really bad happened
644 		 */
645 		goto cleanup;
646 	}
647 
648 	len = sendto(s1, repdata.data, repdata.length, 0,
649 		    (struct sockaddr *)&from, sizeof (from));
650 
651 	if (len < repdata.length) {
652 		krb5_xfree(repdata.data);
653 
654 		krb5_klog_syslog(LOG_ERR, gettext("chpw: Error sending reply:"
655 				" %s"), error_message(errno));
656 		goto cleanup;
657 	}
658 
659 	if (repdata.data != NULL)
660 		krb5_xfree(repdata.data);
661 cleanup:
662 	krb5_kt_close(context, kt);
663 }
664