1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 #include "k5-int.h"
3 #include <kadm5/admin.h>
4 #include <syslog.h>
5 #include <adm_proto.h> /* krb5_klog_syslog */
6 #include <stdio.h>
7 #include <errno.h>
8
9 #include "kadm5/server_internal.h" /* XXX for kadm5_server_handle_t */
10
11 #include "misc.h"
12
13 #ifndef GETSOCKNAME_ARG3_TYPE
14 #define GETSOCKNAME_ARG3_TYPE int
15 #endif
16
17 #define RFC3244_VERSION 0xff80
18
19 static krb5_error_code
process_chpw_request(krb5_context context,void * server_handle,char * realm,krb5_keytab keytab,const krb5_fulladdr * local_addr,const krb5_fulladdr * remote_addr,krb5_data * req,krb5_data * rep)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,
23 krb5_data *rep)
24 {
25 krb5_error_code ret;
26 char *ptr;
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;
35 krb5_error krberror;
36 int numresult;
37 char strresult[1024];
38 char *clientstr = NULL, *targetstr = NULL;
39 const char *errmsg = NULL;
40 size_t clen;
41 char *cdots;
42 struct sockaddr_storage ss;
43 socklen_t salen;
44 char addrbuf[100];
45 krb5_address *addr = remote_addr->address;
46
47 *rep = empty_data();
48
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));
55 goto bailout;
56 }
57
58 ptr = req->data;
59
60 /* verify length */
61
62 plen = (*ptr++ & 0xff);
63 plen = (plen<<8) | (*ptr++ & 0xff);
64
65 if (plen != req->length) {
66 ret = KRB5KRB_AP_ERR_MODIFIED;
67 numresult = KRB5_KPASSWD_MALFORMED;
68 strlcpy(strresult, "Request length was inconsistent",
69 sizeof(strresult));
70 goto bailout;
71 }
72
73 /* verify version number */
74
75 vno = (*ptr++ & 0xff) ;
76 vno = (vno<<8) | (*ptr++ & 0xff);
77
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);
83 goto bailout;
84 }
85
86 /* read, check ap-req length */
87
88 ap_req.length = (*ptr++ & 0xff);
89 ap_req.length = (ap_req.length<<8) | (*ptr++ & 0xff);
90
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",
95 sizeof(strresult));
96 goto bailout;
97 }
98
99 /* verify ap_req */
100
101 ap_req.data = ptr;
102 ptr += ap_req.length;
103
104 ret = krb5_auth_con_init(context, &auth_context);
105 if (ret) {
106 numresult = KRB5_KPASSWD_HARDERROR;
107 strlcpy(strresult, "Failed initializing auth context",
108 sizeof(strresult));
109 goto chpwfail;
110 }
111
112 ret = krb5_auth_con_setflags(context, auth_context,
113 KRB5_AUTH_CONTEXT_DO_SEQUENCE);
114 if (ret) {
115 numresult = KRB5_KPASSWD_HARDERROR;
116 strlcpy(strresult, "Failed initializing auth context",
117 sizeof(strresult));
118 goto chpwfail;
119 }
120
121 ret = krb5_build_principal(context, &changepw, strlen(realm), realm,
122 "kadmin", "changepw", NULL);
123 if (ret) {
124 numresult = KRB5_KPASSWD_HARDERROR;
125 strlcpy(strresult, "Failed building kadmin/changepw principal",
126 sizeof(strresult));
127 goto chpwfail;
128 }
129
130 ret = krb5_rd_req(context, &auth_context, &ap_req, changepw, keytab,
131 NULL, &ticket);
132
133 if (ret) {
134 numresult = KRB5_KPASSWD_AUTHERROR;
135 strlcpy(strresult, "Failed reading application request",
136 sizeof(strresult));
137 goto chpwfail;
138 }
139
140 /* construct the ap-rep */
141
142 ret = krb5_mk_rep(context, auth_context, &ap_rep);
143 if (ret) {
144 numresult = KRB5_KPASSWD_AUTHERROR;
145 strlcpy(strresult, "Failed replying to application request",
146 sizeof(strresult));
147 goto chpwfail;
148 }
149
150 /* decrypt the ChangePasswdData */
151
152 cipher.length = (req->data + req->length) - ptr;
153 cipher.data = ptr;
154
155 /*
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.
161 */
162
163 ret = krb5_rd_priv(context, auth_context, &cipher, &clear, &replay);
164 if (ret) {
165 numresult = KRB5_KPASSWD_HARDERROR;
166 strlcpy(strresult, "Failed decrypting request", sizeof(strresult));
167 goto chpwfail;
168 }
169
170 client = ticket->enc_part2->client;
171
172 /* decode ChangePasswdData for setpw requests */
173 if (vno == RFC3244_VERSION) {
174 krb5_data *clear_data;
175
176 ret = decode_krb5_setpw_req(&clear, &clear_data, &target);
177 if (ret != 0) {
178 numresult = KRB5_KPASSWD_MALFORMED;
179 strlcpy(strresult, "Failed decoding ChangePasswdData",
180 sizeof(strresult));
181 goto chpwfail;
182 }
183
184 zapfree(clear.data, clear.length);
185
186 clear = *clear_data;
187 free(clear_data);
188
189 if (target != NULL) {
190 ret = krb5_unparse_name(context, target, &targetstr);
191 if (ret != 0) {
192 numresult = KRB5_KPASSWD_HARDERROR;
193 strlcpy(strresult, "Failed unparsing target name for log",
194 sizeof(strresult));
195 goto chpwfail;
196 }
197 }
198 }
199
200 ret = krb5_unparse_name(context, client, &clientstr);
201 if (ret) {
202 numresult = KRB5_KPASSWD_HARDERROR;
203 strlcpy(strresult, "Failed unparsing client name for log",
204 sizeof(strresult));
205 goto chpwfail;
206 }
207
208 /* change the password */
209
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));
214 if (ret)
215 errmsg = krb5_get_error_message(context, ret);
216
217 /* zap the password */
218 zapfree(clear.data, clear.length);
219 zapfree(ptr, clear.length);
220 clear = empty_data();
221
222 clen = strlen(clientstr);
223 trunc_name(&clen, &cdots);
224
225 switch (addr->addrtype) {
226 case ADDRTYPE_INET: {
227 struct sockaddr_in *sin = ss2sin(&ss);
228
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);
233 break;
234 }
235 case ADDRTYPE_INET6: {
236 struct sockaddr_in6 *sin6 = ss2sin6(&ss);
237
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);
242 break;
243 }
244 default: {
245 struct sockaddr *sa = ss2sa(&ss);
246
247 sa->sa_family = AF_UNSPEC;
248 salen = sizeof(*sa);
249 break;
250 }
251 }
252
253 if (getnameinfo(ss2sa(&ss), salen,
254 addrbuf, sizeof(addrbuf), NULL, 0,
255 NI_NUMERICHOST | NI_NUMERICSERV) != 0)
256 strlcpy(addrbuf, "<unprintable>", sizeof(addrbuf));
257
258 if (vno == RFC3244_VERSION) {
259 size_t tlen;
260 char *tdots;
261 const char *targetp;
262
263 if (target == NULL) {
264 tlen = clen;
265 tdots = cdots;
266 targetp = targetstr;
267 } else {
268 tlen = strlen(targetstr);
269 trunc_name(&tlen, &tdots);
270 targetp = clientstr;
271 }
272
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");
277 } else {
278 krb5_klog_syslog(LOG_NOTICE, _("chpw request from %s for %.*s%s: %s"),
279 addrbuf, (int) clen, clientstr, cdots,
280 errmsg ? errmsg : "success");
281 }
282 switch (ret) {
283 case KADM5_AUTH_CHANGEPW:
284 numresult = KRB5_KPASSWD_ACCESSDENIED;
285 break;
286 case KADM5_AUTH_INITIAL:
287 numresult = KRB5_KPASSWD_INITIAL_FLAG_NEEDED;
288 break;
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;
296 break;
297 case 0:
298 numresult = KRB5_KPASSWD_SUCCESS;
299 strlcpy(strresult, "", sizeof(strresult));
300 break;
301 default:
302 numresult = KRB5_KPASSWD_HARDERROR;
303 break;
304 }
305
306 chpwfail:
307
308 ret = alloc_data(&clear, 2 + strlen(strresult));
309 if (ret)
310 goto bailout;
311
312 ptr = clear.data;
313
314 *ptr++ = (numresult>>8) & 0xff;
315 *ptr++ = numresult & 0xff;
316
317 memcpy(ptr, strresult, strlen(strresult));
318
319 cipher = empty_data();
320
321 if (ap_rep.length) {
322 ret = krb5_auth_con_setaddrs(context, auth_context,
323 local_addr->address, NULL);
324 if (ret) {
325 numresult = KRB5_KPASSWD_HARDERROR;
326 strlcpy(strresult,
327 "Failed storing client and server internet addresses",
328 sizeof(strresult));
329 } else {
330 ret = krb5_mk_priv(context, auth_context, &clear, &cipher,
331 &replay);
332 if (ret) {
333 numresult = KRB5_KPASSWD_HARDERROR;
334 strlcpy(strresult, "Failed encrypting reply",
335 sizeof(strresult));
336 }
337 }
338 }
339
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. */
342
343 if (cipher.length == 0) {
344 /* clear out ap_rep now, so that it won't be inserted in the
345 reply */
346
347 if (ap_rep.length) {
348 free(ap_rep.data);
349 ap_rep = empty_data();
350 }
351
352 krberror.ctime = 0;
353 krberror.cusec = 0;
354 krberror.susec = 0;
355 ret = krb5_timeofday(context, &krberror.stime);
356 if (ret)
357 goto bailout;
358
359 /* this is really icky. but it's what all the other callers
360 to mk_error do. */
361 krberror.error = ret;
362 krberror.error -= ERROR_TABLE_BASE_krb5;
363 if (krberror.error > KRB_ERR_MAX)
364 krberror.error = KRB_ERR_GENERIC;
365
366 krberror.client = NULL;
367
368 ret = krb5_build_principal(context, &krberror.server,
369 strlen(realm), realm,
370 "kadmin", "changepw", NULL);
371 if (ret)
372 goto bailout;
373 krberror.text.length = 0;
374 krberror.e_data = clear;
375
376 ret = krb5_mk_error(context, &krberror, &cipher);
377
378 krb5_free_principal(context, krberror.server);
379
380 if (ret)
381 goto bailout;
382 }
383
384 /* construct the reply */
385
386 ret = alloc_data(rep, 6 + ap_rep.length + cipher.length);
387 if (ret)
388 goto bailout;
389 ptr = rep->data;
390
391 /* length */
392
393 *ptr++ = (rep->length>>8) & 0xff;
394 *ptr++ = rep->length & 0xff;
395
396 /* version == 0x0001 big-endian */
397
398 *ptr++ = 0;
399 *ptr++ = 1;
400
401 /* ap_rep length, big-endian */
402
403 *ptr++ = (ap_rep.length>>8) & 0xff;
404 *ptr++ = ap_rep.length & 0xff;
405
406 /* ap-rep data */
407
408 if (ap_rep.length) {
409 memcpy(ptr, ap_rep.data, ap_rep.length);
410 ptr += ap_rep.length;
411 }
412
413 /* krb-priv or krb-error */
414
415 memcpy(ptr, cipher.data, cipher.length);
416
417 bailout:
418 krb5_auth_con_free(context, auth_context);
419 krb5_free_principal(context, changepw);
420 krb5_free_ticket(context, ticket);
421 free(ap_rep.data);
422 free(clear.data);
423 free(cipher.data);
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);
428 return ret;
429 }
430
431 /* Dispatch routine for set/change password */
432 void
dispatch(void * handle,const krb5_fulladdr * local_addr,const krb5_fulladdr * remote_addr,krb5_data * request,int is_tcp,verto_ctx * vctx,loop_respond_fn respond,void * arg)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)
436 {
437 krb5_error_code ret;
438 krb5_keytab kt = NULL;
439 kadm5_server_handle_t server_handle = *(void **)handle;
440 krb5_data *response = NULL;
441 const char *emsg;
442
443 ret = krb5_kt_resolve(server_handle->context, "KDB:", &kt);
444 if (ret != 0) {
445 emsg = krb5_get_error_message(server_handle->context, ret);
446 krb5_klog_syslog(LOG_ERR, _("chpw: Couldn't open admin keytab %s"),
447 emsg);
448 krb5_free_error_message(server_handle->context, emsg);
449 goto egress;
450 }
451
452 response = k5alloc(sizeof(krb5_data), &ret);
453 if (response == NULL)
454 goto egress;
455
456 ret = process_chpw_request(server_handle->context,
457 server_handle,
458 server_handle->params.realm,
459 kt,
460 local_addr,
461 remote_addr,
462 request,
463 response);
464 egress:
465 if (ret)
466 krb5_free_data(server_handle->context, response);
467 krb5_kt_close(server_handle->context, kt);
468 (*respond)(arg, ret, ret == 0 ? response : NULL);
469 }
470