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