2 * Copyright 2010 Sun Microsystems, Inc. All rights reserved.
3 * Use is subject to license terms.
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.
39 #include <kadm5/admin.h>
41 #include <krb5/adm_proto.h>
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
,
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
;
60 krb5_data cipher
, clear
;
61 struct sockaddr local_addr
, remote_addr
;
63 krb5_replay_data replay
;
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",
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);
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",
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",
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"),
147 numresult
= KRB5_KPASSWD_HARDERROR
;
148 (void) strlcpy(strresult
, "Failed initializing auth context",
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"),
160 numresult
= KRB5_KPASSWD_HARDERROR
;
161 (void) strlcpy(strresult
, "Failed initializing auth context",
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 "
173 numresult
= KRB5_KPASSWD_HARDERROR
;
174 (void) strlcpy(strresult
,
175 "Failed building kadmin/changepw principal",
180 ret
= krb5_rd_req(context
, &auth_context
, &ap_req
, changepw
, keytab
,
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
));
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\""),
198 krb5_klog_syslog(LOG_ERR
,
199 gettext("Change password request failed because "
200 "keytab file \"%s\" does not exist"),
204 krb5_klog_syslog(LOG_ERR
,
205 gettext("Change password request failed. "
206 "Failed to parse Kerberos AP_REQ message: %s"),
210 numresult
= KRB5_KPASSWD_AUTHERROR
;
211 (void) strlcpy(strresult
, "Failed reading application request",
217 * Set up address info
219 addrlen
= sizeof (local_addr
);
221 if (getsockname(s
, &local_addr
, &addrlen
) < 0) {
223 numresult
= KRB5_KPASSWD_HARDERROR
;
224 (void) strlcpy(strresult
,
225 "Failed getting server internet address",
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
);
239 local_kaddr
.contents
= (krb5_octet
*) &(((struct sockaddr_in
*)&local_addr
)->sin_addr
);
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) {
250 numresult
= KRB5_KPASSWD_HARDERROR
;
251 (void) strlcpy(strresult
,
252 "Malloc failed for local_kaddr",
257 (void) memcpy(local_kaddr
.contents
, addrs
[0]->contents
,
261 krb5_free_addresses(context
, addrs
);
264 addrlen
= sizeof (remote_addr
);
266 if (getpeername(s
, &remote_addr
, &addrlen
) < 0) {
268 numresult
= KRB5_KPASSWD_HARDERROR
;
269 (void) strlcpy(strresult
,
270 "Failed getting client internet address",
275 remote_kaddr
.addrtype
= ADDRTYPE_INET
;
276 remote_kaddr
.length
= sizeof (((struct sockaddr_in
*)
277 &remote_addr
)->sin_addr
);
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
,
301 numresult
= KRB5_KPASSWD_HARDERROR
;
302 (void) strlcpy(strresult
,
303 "Failed storing client internet address",
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",
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",
331 * Decrypt the new password
333 cipher
.length
= (req
->data
+ req
->length
) - ptr
;
336 if (ret
= krb5_rd_priv(context
, auth_context
, &cipher
,
338 numresult
= KRB5_KPASSWD_HARDERROR
;
339 (void) strlcpy(strresult
, "Failed decrypting request",
344 ret
= krb5_unparse_name(context
, ticket
->enc_part2
->client
, &clientstr
);
346 numresult
= KRB5_KPASSWD_HARDERROR
;
347 (void) strcpy(strresult
, "Failed decrypting request");
352 * Change the password
354 if ((ptr
= (char *)malloc(clear
.length
+ 1)) == NULL
) {
356 numresult
= KRB5_KPASSWD_HARDERROR
;
357 (void) strlcpy(strresult
, "Malloc failed for ptr",
359 krb5_free_unparsed_name(context
, clientstr
);
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
,
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
);
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
);
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
;
397 numresult
= KRB5_KPASSWD_SOFTERROR
;
399 * strresult set by kadb5_chpass_principal_util()
407 numresult
= KRB5_KPASSWD_SUCCESS
;
408 (void) strlcpy(strresult
, "", sizeof (strresult
));
412 clear
.length
= 2 + strlen(strresult
);
413 if (clear
.data
!= NULL
) {
414 krb5_xfree(clear
.data
);
417 if ((clear
.data
= (char *)malloc(clear
.length
)) == NULL
) {
419 numresult
= KRB5_KPASSWD_HARDERROR
;
420 (void) strlcpy(strresult
, "Malloc failed for clear.data",
426 *ptr
++ = (numresult
>>8) & 0xff;
427 *ptr
++ = numresult
& 0xff;
429 (void) memcpy(ptr
, strresult
, strlen(strresult
));
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",
441 if (ret
= krb5_mk_priv(context
, auth_context
, &clear
,
443 numresult
= KRB5_KPASSWD_HARDERROR
;
444 (void) strlcpy(strresult
,
445 "Failed encrypting reply",
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
461 if (ap_rep
.data
!= NULL
)
462 krb5_xfree(ap_rep
.data
);
470 if (ret
= krb5_timeofday(context
, &krberror
.stime
))
474 * This is really icky. but it's what all the other callers
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
)) {
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
);
501 * Construct the reply
503 rep
->length
= 6 + ap_rep
.length
+ cipher
.length
;
504 if ((rep
->data
= (char *)malloc(rep
->length
)) == NULL
) {
513 *ptr
++ = (rep
->length
>>8) & 0xff;
514 *ptr
++ = rep
->length
& 0xff;
517 * Version == 0x0001 big-endian
523 * ap_rep length, big-endian
525 *ptr
++ = (ap_rep
.length
>>8) & 0xff;
526 *ptr
++ = ap_rep
.length
& 0xff;
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
);
543 krb5_auth_con_free(context
, auth_context
);
545 krb5_free_principal(context
, changepw
);
546 if (ap_rep
.data
!= NULL
)
547 krb5_xfree(ap_rep
.data
);
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
);
555 krb5_xfree(local_kaddr
.contents
);
562 * This routine is used to handle password-change requests received
563 * on kpasswd-port 464 from MIT/M$ clients.
566 handle_chpw(krb5_context context
, int s1
,
567 void *serverhandle
, kadm5_config_params
*params
)
572 struct sockaddr_in from
;
575 krb5_data reqdata
, repdata
;
583 fromlen
= sizeof (from
);
585 if ((len
= recvfrom(s1
, req
, sizeof (req
), 0, (struct sockaddr
*)&from
,
587 krb5_klog_syslog(LOG_ERR
, gettext("chpw: Couldn't receive "
588 "request: %s"), error_message(errno
));
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
));
604 reqdata
.length
= len
;
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
));
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
));
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
));
649 if (repdata
.length
== 0 || repdata
.data
== NULL
) {
651 * Just return. This means something really bad happened
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
));
667 if (repdata
.data
!= NULL
)
668 krb5_xfree(repdata
.data
);
670 krb5_kt_close(context
, kt
);