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