dmake: do not set MAKEFLAGS=k
[unleashed/tickless.git] / usr / src / lib / krb5 / kadm5 / srv / chgpwd.c
blob86f17d82de8b673673ac6b44715187a538b488d4
1 /*
2 * Copyright 2010 Sun Microsystems, Inc. All rights reserved.
3 * Use is subject to license terms.
4 */
6 /*
7 * lib/krb5/kadm5/srv/chgpwd.c
9 * Copyright 1998 by the Massachusetts Institute of Technology.
10 * All Rights Reserved.
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.
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.
34 * chgpwd.c - Handles changepw requests issued from non-Solaris krb5 clients.
37 #include <libintl.h>
38 #include <locale.h>
39 #include <kadm5/admin.h>
40 #include <syslog.h>
41 #include <krb5/adm_proto.h>
43 #define MAXAPREQ 1500
45 static krb5_error_code
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)
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 socklen_t 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;
71 ret = 0;
72 rep->length = 0;
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;
84 if (req->length < 4) {
86 * either this, or the server is printing bad messages,
87 * or the caller passed in garbage
89 ret = KRB5KRB_AP_ERR_MODIFIED;
90 numresult = KRB5_KPASSWD_MALFORMED;
91 (void) strlcpy(strresult, "Request was truncated",
92 sizeof (strresult));
93 goto chpwfail;
96 ptr = req->data;
99 * Verify length
101 plen = (*ptr++ & 0xff);
102 plen = (plen<<8) | (*ptr++ & 0xff);
104 if (plen != req->length)
105 return (KRB5KRB_AP_ERR_MODIFIED);
108 * Verify version number
110 vno = (*ptr++ & 0xff);
111 vno = (vno<<8) | (*ptr++ & 0xff);
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;
123 * Read, check ap-req length
125 ap_req.length = (*ptr++ & 0xff);
126 ap_req.length = (ap_req.length<<8) | (*ptr++ & 0xff);
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;
137 * Verify ap_req
139 ap_req.data = ptr;
140 ptr += ap_req.length;
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;
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;
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;
180 ret = krb5_rd_req(context, &auth_context, &ap_req, changepw, keytab,
181 NULL, &ticket);
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));
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));
210 numresult = KRB5_KPASSWD_AUTHERROR;
211 (void) strlcpy(strresult, "Failed reading application request",
212 sizeof (strresult));
213 goto chpwfail;
217 * Set up address info
219 addrlen = sizeof (local_addr);
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;
231 * Some brain-dead OS's don't return useful information from
232 * the getsockname call. Namely, windows and solaris.
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;
243 krb5_os_localaddr(context, &addrs);
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;
257 (void) memcpy(local_kaddr.contents, addrs[0]->contents,
258 addrs[0]->length);
259 allocated_mem++;
261 krb5_free_addresses(context, addrs);
264 addrlen = sizeof (remote_addr);
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;
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;
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?
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;
309 * Verify that this is an AS_REQ ticket
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;
320 * Construct the ap-rep
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;
331 * Decrypt the new password
333 cipher.length = (req->data + req->length) - ptr;
334 cipher.data = ptr;
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;
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;
352 * Change the password
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;
362 (void) memcpy(ptr, clear.data, clear.length);
363 ptr[clear.length] = '\0';
365 ret = (kadm5_ret_t)kadm5_chpass_principal_util(server_handle,
366 ticket->enc_part2->client,
367 ptr, NULL, strresult,
368 sizeof (strresult));
371 * Zap the password
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;
379 free(ptr);
380 clear.length = 0;
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);
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;
399 * strresult set by kadb5_chpass_principal_util()
401 goto chpwfail;
405 * Success!
407 numresult = KRB5_KPASSWD_SUCCESS;
408 (void) strlcpy(strresult, "", sizeof (strresult));
410 chpwfail:
412 clear.length = 2 + strlen(strresult);
413 if (clear.data != NULL) {
414 krb5_xfree(clear.data);
415 clear.data = NULL;
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));
424 ptr = clear.data;
426 *ptr++ = (numresult>>8) & 0xff;
427 *ptr++ = numresult & 0xff;
429 (void) memcpy(ptr, strresult, strlen(strresult));
431 cipher.length = 0;
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));
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.
455 if (cipher.length == 0) {
457 * Clear out ap_rep now, so that it won't be inserted
458 * in the reply
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;
467 krberror.ctime = 0;
468 krberror.cusec = 0;
469 krberror.susec = 0;
470 if (ret = krb5_timeofday(context, &krberror.stime))
471 goto bailout;
474 * This is really icky. but it's what all the other callers
475 * to mk_error do.
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;
482 krberror.client = NULL;
483 if (ret = krb5_build_principal(context, &krberror.server,
484 strlen(realm), realm,
485 "kadmin", "changepw", NULL)) {
486 goto bailout;
489 krberror.text.length = 0;
490 krberror.e_data = clear;
492 ret = krb5_mk_error(context, &krberror, &cipher);
494 krb5_free_principal(context, krberror.server);
496 if (ret)
497 goto bailout;
501 * Construct the reply
503 rep->length = 6 + ap_rep.length + cipher.length;
504 if ((rep->data = (char *)malloc(rep->length)) == NULL) {
505 ret = errno;
506 goto bailout;
508 ptr = rep->data;
511 * Length
513 *ptr++ = (rep->length>>8) & 0xff;
514 *ptr++ = rep->length & 0xff;
517 * Version == 0x0001 big-endian
519 *ptr++ = 0;
520 *ptr++ = 1;
523 * ap_rep length, big-endian
525 *ptr++ = (ap_rep.length>>8) & 0xff;
526 *ptr++ = ap_rep.length & 0xff;
529 * ap-rep data
531 if (ap_rep.length) {
532 (void) memcpy(ptr, ap_rep.data, ap_rep.length);
533 ptr += ap_rep.length;
537 * krb-priv or krb-error
539 (void) memcpy(ptr, cipher.data, cipher.length);
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);
557 return (ret);
562 * This routine is used to handle password-change requests received
563 * on kpasswd-port 464 from MIT/M$ clients.
565 void
566 handle_chpw(krb5_context context, int s1,
567 void *serverhandle, kadm5_config_params *params)
569 krb5_error_code ret;
570 char req[MAXAPREQ];
571 int len;
572 struct sockaddr_in from;
573 socklen_t fromlen;
574 krb5_keytab kt;
575 krb5_data reqdata, repdata;
576 int s2 = -1;
578 reqdata.length = 0;
579 reqdata.data = NULL;
580 repdata.length = 0;
581 repdata.data = NULL;
583 fromlen = sizeof (from);
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;
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.
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;
604 reqdata.length = len;
605 reqdata.data = req;
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.
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.
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.
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;
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;
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));
646 if (s2 > 0)
647 (void) close(s2);
649 if (repdata.length == 0 || repdata.data == NULL) {
651 * Just return. This means something really bad happened
653 goto cleanup;
656 len = sendto(s1, repdata.data, repdata.length, 0,
657 (struct sockaddr *)&from, sizeof (from));
659 if (len < repdata.length) {
660 krb5_xfree(repdata.data);
662 krb5_klog_syslog(LOG_ERR, gettext("chpw: Error sending reply:"
663 " %s"), error_message(errno));
664 goto cleanup;
667 if (repdata.data != NULL)
668 krb5_xfree(repdata.data);
669 cleanup:
670 krb5_kt_close(context, kt);