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 struct sockaddr * local_addr,const struct sockaddr * 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 struct sockaddr *local_addr,
22 const struct sockaddr *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 krb5_address laddr;
37 int numresult;
38 char strresult[1024];
39 char *clientstr = NULL, *targetstr = NULL;
40 const char *errmsg = NULL;
41 size_t clen;
42 char *cdots;
43 char addrbuf[128];
44
45 *rep = empty_data();
46
47 if (req->length < 4) {
48 /* either this, or the server is printing bad messages,
49 or the caller passed in garbage */
50 ret = KRB5KRB_AP_ERR_MODIFIED;
51 numresult = KRB5_KPASSWD_MALFORMED;
52 strlcpy(strresult, "Request was truncated", sizeof(strresult));
53 goto bailout;
54 }
55
56 ptr = req->data;
57
58 /* verify length */
59
60 plen = (*ptr++ & 0xff);
61 plen = (plen<<8) | (*ptr++ & 0xff);
62
63 if (plen != req->length) {
64 ret = KRB5KRB_AP_ERR_MODIFIED;
65 numresult = KRB5_KPASSWD_MALFORMED;
66 strlcpy(strresult, "Request length was inconsistent",
67 sizeof(strresult));
68 goto bailout;
69 }
70
71 /* verify version number */
72
73 vno = (*ptr++ & 0xff) ;
74 vno = (vno<<8) | (*ptr++ & 0xff);
75
76 if (vno != 1 && vno != RFC3244_VERSION) {
77 ret = KRB5KDC_ERR_BAD_PVNO;
78 numresult = KRB5_KPASSWD_BAD_VERSION;
79 snprintf(strresult, sizeof(strresult),
80 "Request contained unknown protocol version number %d", vno);
81 goto bailout;
82 }
83
84 /* read, check ap-req length */
85
86 ap_req.length = (*ptr++ & 0xff);
87 ap_req.length = (ap_req.length<<8) | (*ptr++ & 0xff);
88
89 if (ptr + ap_req.length >= req->data + req->length) {
90 ret = KRB5KRB_AP_ERR_MODIFIED;
91 numresult = KRB5_KPASSWD_MALFORMED;
92 strlcpy(strresult, "Request was truncated in AP-REQ",
93 sizeof(strresult));
94 goto bailout;
95 }
96
97 /* verify ap_req */
98
99 ap_req.data = ptr;
100 ptr += ap_req.length;
101
102 ret = krb5_auth_con_init(context, &auth_context);
103 if (ret) {
104 numresult = KRB5_KPASSWD_HARDERROR;
105 strlcpy(strresult, "Failed initializing auth context",
106 sizeof(strresult));
107 goto chpwfail;
108 }
109
110 ret = krb5_auth_con_setflags(context, auth_context,
111 KRB5_AUTH_CONTEXT_DO_SEQUENCE);
112 if (ret) {
113 numresult = KRB5_KPASSWD_HARDERROR;
114 strlcpy(strresult, "Failed initializing auth context",
115 sizeof(strresult));
116 goto chpwfail;
117 }
118
119 ret = krb5_build_principal(context, &changepw, strlen(realm), realm,
120 "kadmin", "changepw", NULL);
121 if (ret) {
122 numresult = KRB5_KPASSWD_HARDERROR;
123 strlcpy(strresult, "Failed building kadmin/changepw principal",
124 sizeof(strresult));
125 goto chpwfail;
126 }
127
128 ret = krb5_rd_req(context, &auth_context, &ap_req, changepw, keytab,
129 NULL, &ticket);
130
131 if (ret) {
132 numresult = KRB5_KPASSWD_AUTHERROR;
133 strlcpy(strresult, "Failed reading application request",
134 sizeof(strresult));
135 goto chpwfail;
136 }
137
138 /* construct the ap-rep */
139
140 ret = krb5_mk_rep(context, auth_context, &ap_rep);
141 if (ret) {
142 numresult = KRB5_KPASSWD_AUTHERROR;
143 strlcpy(strresult, "Failed replying to application request",
144 sizeof(strresult));
145 goto chpwfail;
146 }
147
148 /* decrypt the ChangePasswdData */
149
150 cipher.length = (req->data + req->length) - ptr;
151 cipher.data = ptr;
152
153 /*
154 * Don't set a remote address in auth_context before calling krb5_rd_priv,
155 * so that we can work against clients behind a NAT. Reflection attacks
156 * aren't a concern since we use sequence numbers and since our requests
157 * don't look anything like our responses. Also don't set a local address,
158 * since we don't know what interface the request was received on.
159 */
160
161 ret = krb5_rd_priv(context, auth_context, &cipher, &clear, &replay);
162 if (ret) {
163 numresult = KRB5_KPASSWD_HARDERROR;
164 strlcpy(strresult, "Failed decrypting request", sizeof(strresult));
165 goto chpwfail;
166 }
167
168 client = ticket->enc_part2->client;
169
170 /* decode ChangePasswdData for setpw requests */
171 if (vno == RFC3244_VERSION) {
172 krb5_data *clear_data;
173
174 ret = decode_krb5_setpw_req(&clear, &clear_data, &target);
175 if (ret != 0) {
176 numresult = KRB5_KPASSWD_MALFORMED;
177 strlcpy(strresult, "Failed decoding ChangePasswdData",
178 sizeof(strresult));
179 goto chpwfail;
180 }
181
182 zapfree(clear.data, clear.length);
183
184 clear = *clear_data;
185 free(clear_data);
186
187 if (target != NULL) {
188 ret = krb5_unparse_name(context, target, &targetstr);
189 if (ret != 0) {
190 numresult = KRB5_KPASSWD_HARDERROR;
191 strlcpy(strresult, "Failed unparsing target name for log",
192 sizeof(strresult));
193 goto chpwfail;
194 }
195 }
196 }
197
198 ret = krb5_unparse_name(context, client, &clientstr);
199 if (ret) {
200 numresult = KRB5_KPASSWD_HARDERROR;
201 strlcpy(strresult, "Failed unparsing client name for log",
202 sizeof(strresult));
203 goto chpwfail;
204 }
205
206 /* change the password */
207
208 ptr = k5memdup0(clear.data, clear.length, &ret);
209 ret = schpw_util_wrapper(server_handle, client, target,
210 (ticket->enc_part2->flags & TKT_FLG_INITIAL) != 0,
211 ptr, NULL, strresult, sizeof(strresult));
212 if (ret)
213 errmsg = krb5_get_error_message(context, ret);
214
215 /* zap the password */
216 zapfree(clear.data, clear.length);
217 zapfree(ptr, clear.length);
218 clear = empty_data();
219
220 clen = strlen(clientstr);
221 trunc_name(&clen, &cdots);
222
223 k5_print_addr(remote_addr, addrbuf, sizeof(addrbuf));
224
225 if (vno == RFC3244_VERSION) {
226 size_t tlen;
227 char *tdots;
228 const char *targetp;
229
230 if (target == NULL) {
231 tlen = clen;
232 tdots = cdots;
233 targetp = targetstr;
234 } else {
235 tlen = strlen(targetstr);
236 trunc_name(&tlen, &tdots);
237 targetp = clientstr;
238 }
239
240 krb5_klog_syslog(LOG_NOTICE, _("setpw request from %s by %.*s%s for "
241 "%.*s%s: %s"), addrbuf, (int) clen,
242 clientstr, cdots, (int) tlen, targetp, tdots,
243 errmsg ? errmsg : "success");
244 } else {
245 krb5_klog_syslog(LOG_NOTICE, _("chpw request from %s for %.*s%s: %s"),
246 addrbuf, (int) clen, clientstr, cdots,
247 errmsg ? errmsg : "success");
248 }
249 switch (ret) {
250 case KADM5_AUTH_CHANGEPW:
251 numresult = KRB5_KPASSWD_ACCESSDENIED;
252 break;
253 case KADM5_AUTH_INITIAL:
254 numresult = KRB5_KPASSWD_INITIAL_FLAG_NEEDED;
255 break;
256 case KADM5_PASS_Q_TOOSHORT:
257 case KADM5_PASS_REUSE:
258 case KADM5_PASS_Q_CLASS:
259 case KADM5_PASS_Q_DICT:
260 case KADM5_PASS_Q_GENERIC:
261 case KADM5_PASS_TOOSOON:
262 numresult = KRB5_KPASSWD_SOFTERROR;
263 break;
264 case 0:
265 numresult = KRB5_KPASSWD_SUCCESS;
266 strlcpy(strresult, "", sizeof(strresult));
267 break;
268 default:
269 numresult = KRB5_KPASSWD_HARDERROR;
270 break;
271 }
272
273 chpwfail:
274
275 ret = alloc_data(&clear, 2 + strlen(strresult));
276 if (ret)
277 goto bailout;
278
279 ptr = clear.data;
280
281 *ptr++ = (numresult>>8) & 0xff;
282 *ptr++ = numresult & 0xff;
283
284 memcpy(ptr, strresult, strlen(strresult));
285
286 cipher = empty_data();
287
288 if (ap_rep.length) {
289 if (k5_sockaddr_to_address(local_addr, FALSE, &laddr) != 0)
290 laddr = k5_addr_directional_accept;
291 ret = krb5_auth_con_setaddrs(context, auth_context, &laddr, NULL);
292 if (ret) {
293 numresult = KRB5_KPASSWD_HARDERROR;
294 strlcpy(strresult,
295 "Failed storing client and server internet addresses",
296 sizeof(strresult));
297 } else {
298 ret = krb5_mk_priv(context, auth_context, &clear, &cipher,
299 &replay);
300 if (ret) {
301 numresult = KRB5_KPASSWD_HARDERROR;
302 strlcpy(strresult, "Failed encrypting reply",
303 sizeof(strresult));
304 }
305 }
306 }
307
308 /* if no KRB-PRIV was constructed, then we need a KRB-ERROR.
309 if this fails, just bail. there's nothing else we can do. */
310
311 if (cipher.length == 0) {
312 /* clear out ap_rep now, so that it won't be inserted in the
313 reply */
314
315 if (ap_rep.length) {
316 free(ap_rep.data);
317 ap_rep = empty_data();
318 }
319
320 krberror.ctime = 0;
321 krberror.cusec = 0;
322 krberror.susec = 0;
323 ret = krb5_timeofday(context, &krberror.stime);
324 if (ret)
325 goto bailout;
326
327 /* this is really icky. but it's what all the other callers
328 to mk_error do. */
329 krberror.error = ret;
330 krberror.error -= ERROR_TABLE_BASE_krb5;
331 if (krberror.error > KRB_ERR_MAX)
332 krberror.error = KRB_ERR_GENERIC;
333
334 krberror.client = NULL;
335
336 ret = krb5_build_principal(context, &krberror.server,
337 strlen(realm), realm,
338 "kadmin", "changepw", NULL);
339 if (ret)
340 goto bailout;
341 krberror.text.length = 0;
342 krberror.e_data = clear;
343
344 ret = krb5_mk_error(context, &krberror, &cipher);
345
346 krb5_free_principal(context, krberror.server);
347
348 if (ret)
349 goto bailout;
350 }
351
352 /* construct the reply */
353
354 ret = alloc_data(rep, 6 + ap_rep.length + cipher.length);
355 if (ret)
356 goto bailout;
357 ptr = rep->data;
358
359 /* length */
360
361 *ptr++ = (rep->length>>8) & 0xff;
362 *ptr++ = rep->length & 0xff;
363
364 /* version == 0x0001 big-endian */
365
366 *ptr++ = 0;
367 *ptr++ = 1;
368
369 /* ap_rep length, big-endian */
370
371 *ptr++ = (ap_rep.length>>8) & 0xff;
372 *ptr++ = ap_rep.length & 0xff;
373
374 /* ap-rep data */
375
376 if (ap_rep.length) {
377 memcpy(ptr, ap_rep.data, ap_rep.length);
378 ptr += ap_rep.length;
379 }
380
381 /* krb-priv or krb-error */
382
383 memcpy(ptr, cipher.data, cipher.length);
384
385 bailout:
386 krb5_auth_con_free(context, auth_context);
387 krb5_free_principal(context, changepw);
388 krb5_free_ticket(context, ticket);
389 free(ap_rep.data);
390 free(clear.data);
391 free(cipher.data);
392 krb5_free_principal(context, target);
393 krb5_free_unparsed_name(context, targetstr);
394 krb5_free_unparsed_name(context, clientstr);
395 krb5_free_error_message(context, errmsg);
396 return ret;
397 }
398
399 /* Dispatch routine for set/change password */
400 void
dispatch(void * handle,const struct sockaddr * local_addr,const struct sockaddr * remote_addr,krb5_data * request,int is_tcp,verto_ctx * vctx,loop_respond_fn respond,void * arg)401 dispatch(void *handle, const struct sockaddr *local_addr,
402 const struct sockaddr *remote_addr, krb5_data *request, int is_tcp,
403 verto_ctx *vctx, loop_respond_fn respond, void *arg)
404 {
405 krb5_error_code ret;
406 krb5_keytab kt = NULL;
407 kadm5_server_handle_t server_handle = *(void **)handle;
408 krb5_data *response = NULL;
409 const char *emsg;
410
411 ret = krb5_kt_resolve(server_handle->context, "KDB:", &kt);
412 if (ret != 0) {
413 emsg = krb5_get_error_message(server_handle->context, ret);
414 krb5_klog_syslog(LOG_ERR, _("chpw: Couldn't open admin keytab %s"),
415 emsg);
416 krb5_free_error_message(server_handle->context, emsg);
417 goto egress;
418 }
419
420 response = k5alloc(sizeof(krb5_data), &ret);
421 if (response == NULL)
422 goto egress;
423
424 ret = process_chpw_request(server_handle->context,
425 server_handle,
426 server_handle->params.realm,
427 kt,
428 local_addr,
429 remote_addr,
430 request,
431 response);
432 egress:
433 if (ret)
434 krb5_free_data(server_handle->context, response);
435 krb5_kt_close(server_handle->context, kt);
436 (*respond)(arg, ret, ret == 0 ? response : NULL);
437 }
438