1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
3 #include <kadm5/admin.h>
5 #include <adm_proto.h> /* krb5_klog_syslog */
9 #include "kadm5/server_internal.h" /* XXX for kadm5_server_handle_t */
13 #ifndef GETSOCKNAME_ARG3_TYPE
14 #define GETSOCKNAME_ARG3_TYPE int
17 #define RFC3244_VERSION 0xff80
19 static krb5_error_code
20 process_chpw_request(krb5_context context, void *server_handle, char *realm,
21 krb5_keytab keytab, const krb5_fulladdr *local_addr,
22 const krb5_fulladdr *remote_addr, krb5_data *req,
27 unsigned int plen, vno;
28 krb5_data ap_req, ap_rep = empty_data();
29 krb5_data cipher = empty_data(), clear = empty_data();
30 krb5_auth_context auth_context = NULL;
31 krb5_principal changepw = NULL;
32 krb5_principal client, target = NULL;
33 krb5_ticket *ticket = NULL;
34 krb5_replay_data replay;
38 char *clientstr = NULL, *targetstr = NULL;
39 const char *errmsg = NULL;
42 struct sockaddr_storage ss;
45 krb5_address *addr = remote_addr->address;
49 if (req->length < 4) {
50 /* either this, or the server is printing bad messages,
51 or the caller passed in garbage */
52 ret = KRB5KRB_AP_ERR_MODIFIED;
53 numresult = KRB5_KPASSWD_MALFORMED;
54 strlcpy(strresult, "Request was truncated", sizeof(strresult));
62 plen = (*ptr++ & 0xff);
63 plen = (plen<<8) | (*ptr++ & 0xff);
65 if (plen != req->length) {
66 ret = KRB5KRB_AP_ERR_MODIFIED;
67 numresult = KRB5_KPASSWD_MALFORMED;
68 strlcpy(strresult, "Request length was inconsistent",
73 /* verify version number */
75 vno = (*ptr++ & 0xff) ;
76 vno = (vno<<8) | (*ptr++ & 0xff);
78 if (vno != 1 && vno != RFC3244_VERSION) {
79 ret = KRB5KDC_ERR_BAD_PVNO;
80 numresult = KRB5_KPASSWD_BAD_VERSION;
81 snprintf(strresult, sizeof(strresult),
82 "Request contained unknown protocol version number %d", vno);
86 /* read, check ap-req length */
88 ap_req.length = (*ptr++ & 0xff);
89 ap_req.length = (ap_req.length<<8) | (*ptr++ & 0xff);
91 if (ptr + ap_req.length >= req->data + req->length) {
92 ret = KRB5KRB_AP_ERR_MODIFIED;
93 numresult = KRB5_KPASSWD_MALFORMED;
94 strlcpy(strresult, "Request was truncated in AP-REQ",
102 ptr += ap_req.length;
104 ret = krb5_auth_con_init(context, &auth_context);
106 numresult = KRB5_KPASSWD_HARDERROR;
107 strlcpy(strresult, "Failed initializing auth context",
112 ret = krb5_auth_con_setflags(context, auth_context,
113 KRB5_AUTH_CONTEXT_DO_SEQUENCE);
115 numresult = KRB5_KPASSWD_HARDERROR;
116 strlcpy(strresult, "Failed initializing auth context",
121 ret = krb5_build_principal(context, &changepw, strlen(realm), realm,
122 "kadmin", "changepw", NULL);
124 numresult = KRB5_KPASSWD_HARDERROR;
125 strlcpy(strresult, "Failed building kadmin/changepw principal",
130 ret = krb5_rd_req(context, &auth_context, &ap_req, changepw, keytab,
134 numresult = KRB5_KPASSWD_AUTHERROR;
135 strlcpy(strresult, "Failed reading application request",
140 /* construct the ap-rep */
142 ret = krb5_mk_rep(context, auth_context, &ap_rep);
144 numresult = KRB5_KPASSWD_AUTHERROR;
145 strlcpy(strresult, "Failed replying to application request",
150 /* decrypt the ChangePasswdData */
152 cipher.length = (req->data + req->length) - ptr;
156 * Don't set a remote address in auth_context before calling krb5_rd_priv,
157 * so that we can work against clients behind a NAT. Reflection attacks
158 * aren't a concern since we use sequence numbers and since our requests
159 * don't look anything like our responses. Also don't set a local address,
160 * since we don't know what interface the request was received on.
163 ret = krb5_rd_priv(context, auth_context, &cipher, &clear, &replay);
165 numresult = KRB5_KPASSWD_HARDERROR;
166 strlcpy(strresult, "Failed decrypting request", sizeof(strresult));
170 client = ticket->enc_part2->client;
172 /* decode ChangePasswdData for setpw requests */
173 if (vno == RFC3244_VERSION) {
174 krb5_data *clear_data;
176 ret = decode_krb5_setpw_req(&clear, &clear_data, &target);
178 numresult = KRB5_KPASSWD_MALFORMED;
179 strlcpy(strresult, "Failed decoding ChangePasswdData",
184 zapfree(clear.data, clear.length);
189 if (target != NULL) {
190 ret = krb5_unparse_name(context, target, &targetstr);
192 numresult = KRB5_KPASSWD_HARDERROR;
193 strlcpy(strresult, "Failed unparsing target name for log",
200 ret = krb5_unparse_name(context, client, &clientstr);
202 numresult = KRB5_KPASSWD_HARDERROR;
203 strlcpy(strresult, "Failed unparsing client name for log",
208 /* change the password */
210 ptr = k5memdup0(clear.data, clear.length, &ret);
211 ret = schpw_util_wrapper(server_handle, client, target,
212 (ticket->enc_part2->flags & TKT_FLG_INITIAL) != 0,
213 ptr, NULL, strresult, sizeof(strresult));
215 errmsg = krb5_get_error_message(context, ret);
217 /* zap the password */
218 zapfree(clear.data, clear.length);
219 zapfree(ptr, clear.length);
220 clear = empty_data();
222 clen = strlen(clientstr);
223 trunc_name(&clen, &cdots);
225 switch (addr->addrtype) {
226 case ADDRTYPE_INET: {
227 struct sockaddr_in *sin = ss2sin(&ss);
229 sin->sin_family = AF_INET;
230 memcpy(&sin->sin_addr, addr->contents, addr->length);
231 sin->sin_port = htons(remote_addr->port);
232 salen = sizeof(*sin);
235 case ADDRTYPE_INET6: {
236 struct sockaddr_in6 *sin6 = ss2sin6(&ss);
238 sin6->sin6_family = AF_INET6;
239 memcpy(&sin6->sin6_addr, addr->contents, addr->length);
240 sin6->sin6_port = htons(remote_addr->port);
241 salen = sizeof(*sin6);
245 struct sockaddr *sa = ss2sa(&ss);
247 sa->sa_family = AF_UNSPEC;
253 if (getnameinfo(ss2sa(&ss), salen,
254 addrbuf, sizeof(addrbuf), NULL, 0,
255 NI_NUMERICHOST | NI_NUMERICSERV) != 0)
256 strlcpy(addrbuf, "<unprintable>", sizeof(addrbuf));
258 if (vno == RFC3244_VERSION) {
263 if (target == NULL) {
268 tlen = strlen(targetstr);
269 trunc_name(&tlen, &tdots);
273 krb5_klog_syslog(LOG_NOTICE, _("setpw request from %s by %.*s%s for "
274 "%.*s%s: %s"), addrbuf, (int) clen,
275 clientstr, cdots, (int) tlen, targetp, tdots,
276 errmsg ? errmsg : "success");
278 krb5_klog_syslog(LOG_NOTICE, _("chpw request from %s for %.*s%s: %s"),
279 addrbuf, (int) clen, clientstr, cdots,
280 errmsg ? errmsg : "success");
283 case KADM5_AUTH_CHANGEPW:
284 numresult = KRB5_KPASSWD_ACCESSDENIED;
286 case KADM5_AUTH_INITIAL:
287 numresult = KRB5_KPASSWD_INITIAL_FLAG_NEEDED;
289 case KADM5_PASS_Q_TOOSHORT:
290 case KADM5_PASS_REUSE:
291 case KADM5_PASS_Q_CLASS:
292 case KADM5_PASS_Q_DICT:
293 case KADM5_PASS_Q_GENERIC:
294 case KADM5_PASS_TOOSOON:
295 numresult = KRB5_KPASSWD_SOFTERROR;
298 numresult = KRB5_KPASSWD_SUCCESS;
299 strlcpy(strresult, "", sizeof(strresult));
302 numresult = KRB5_KPASSWD_HARDERROR;
308 ret = alloc_data(&clear, 2 + strlen(strresult));
314 *ptr++ = (numresult>>8) & 0xff;
315 *ptr++ = numresult & 0xff;
317 memcpy(ptr, strresult, strlen(strresult));
319 cipher = empty_data();
322 ret = krb5_auth_con_setaddrs(context, auth_context,
323 local_addr->address, NULL);
325 numresult = KRB5_KPASSWD_HARDERROR;
327 "Failed storing client and server internet addresses",
330 ret = krb5_mk_priv(context, auth_context, &clear, &cipher,
333 numresult = KRB5_KPASSWD_HARDERROR;
334 strlcpy(strresult, "Failed encrypting reply",
340 /* if no KRB-PRIV was constructed, then we need a KRB-ERROR.
341 if this fails, just bail. there's nothing else we can do. */
343 if (cipher.length == 0) {
344 /* clear out ap_rep now, so that it won't be inserted in the
349 ap_rep = empty_data();
355 ret = krb5_timeofday(context, &krberror.stime);
359 /* this is really icky. but it's what all the other callers
361 krberror.error = ret;
362 krberror.error -= ERROR_TABLE_BASE_krb5;
363 if (krberror.error > KRB_ERR_MAX)
364 krberror.error = KRB_ERR_GENERIC;
366 krberror.client = NULL;
368 ret = krb5_build_principal(context, &krberror.server,
369 strlen(realm), realm,
370 "kadmin", "changepw", NULL);
373 krberror.text.length = 0;
374 krberror.e_data = clear;
376 ret = krb5_mk_error(context, &krberror, &cipher);
378 krb5_free_principal(context, krberror.server);
384 /* construct the reply */
386 ret = alloc_data(rep, 6 + ap_rep.length + cipher.length);
393 *ptr++ = (rep->length>>8) & 0xff;
394 *ptr++ = rep->length & 0xff;
396 /* version == 0x0001 big-endian */
401 /* ap_rep length, big-endian */
403 *ptr++ = (ap_rep.length>>8) & 0xff;
404 *ptr++ = ap_rep.length & 0xff;
409 memcpy(ptr, ap_rep.data, ap_rep.length);
410 ptr += ap_rep.length;
413 /* krb-priv or krb-error */
415 memcpy(ptr, cipher.data, cipher.length);
418 krb5_auth_con_free(context, auth_context);
419 krb5_free_principal(context, changepw);
420 krb5_free_ticket(context, ticket);
424 krb5_free_principal(context, target);
425 krb5_free_unparsed_name(context, targetstr);
426 krb5_free_unparsed_name(context, clientstr);
427 krb5_free_error_message(context, errmsg);
431 /* Dispatch routine for set/change password */
433 dispatch(void *handle, const krb5_fulladdr *local_addr,
434 const krb5_fulladdr *remote_addr, krb5_data *request, int is_tcp,
435 verto_ctx *vctx, loop_respond_fn respond, void *arg)
438 krb5_keytab kt = NULL;
439 kadm5_server_handle_t server_handle = (kadm5_server_handle_t)handle;
440 krb5_data *response = NULL;
443 ret = krb5_kt_resolve(server_handle->context, "KDB:", &kt);
445 emsg = krb5_get_error_message(server_handle->context, ret);
446 krb5_klog_syslog(LOG_ERR, _("chpw: Couldn't open admin keytab %s"),
448 krb5_free_error_message(server_handle->context, emsg);
452 response = k5alloc(sizeof(krb5_data), &ret);
453 if (response == NULL)
456 ret = process_chpw_request(server_handle->context,
458 server_handle->params.realm,
466 krb5_free_data(server_handle->context, response);
467 krb5_kt_close(server_handle->context, kt);
468 (*respond)(arg, ret, ret == 0 ? response : NULL);