tests: Use here-doc kadmin in Java test
[heimdal.git] / kadmin / server.c
blob281822a30fc08d941a4cf7b6d67405822e98ed09
1 /*
2 * Copyright (c) 1997 - 2005 Kungliga Tekniska Högskolan
3 * (Royal Institute of Technology, Stockholm, Sweden).
4 * All rights reserved.
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
17 * 3. Neither the name of the Institute nor the names of its contributors
18 * may be used to endorse or promote products derived from this software
19 * without specific prior written permission.
21 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
34 #include "kadmin_locl.h"
35 #include <krb5-private.h>
37 static kadm5_ret_t check_aliases(kadm5_server_context *,
38 kadm5_principal_ent_rec *,
39 kadm5_principal_ent_rec *);
42 * All the iter_cb stuff is about online listing of principals via
43 * kadm5_iter_principals(). Search for "LIST" to see more commentary.
45 struct iter_cb_data {
46 krb5_context context;
47 krb5_auth_context ac;
48 krb5_storage *rsp;
49 kadm5_ret_t ret;
50 size_t n;
51 size_t i;
52 int fd;
53 unsigned int initial:1;
54 unsigned int stop:1;
58 * This function sends the current chunk of principal listing and checks if the
59 * client requested that the listing stop.
61 static int
62 iter_cb_send_now(struct iter_cb_data *d)
64 struct timeval tv;
65 krb5_data out;
67 krb5_data_zero(&out);
69 if (!d->stop) {
70 fd_set fds;
71 int nfds;
74 * The client can send us one message to interrupt the iteration.
76 * TODO: Maybe we should have the client send a message every N chunks
77 * so we can clock the listing and have a chance to receive any
78 * interrupt message from the client?
80 FD_ZERO(&fds);
81 FD_SET(d->fd, &fds);
82 tv.tv_sec = 0;
83 tv.tv_usec = 0;
84 nfds = select(d->fd + 1, &fds, NULL, NULL, &tv);
85 if (nfds == -1) {
86 d->ret = errno;
87 } else if (nfds > 0) {
89 * And it did. We'll throw this message away. It should be a NOP
90 * call, which we'd throw away anyways. If the client's stop
91 * message arrives after we're done anyways, well, it will be
92 * processed as a NOP and thrown away.
94 d->stop = 1;
95 d->ret = krb5_read_priv_message(d->context, d->ac, &d->fd, &out);
96 krb5_data_free(&out);
97 if (d->ret == HEIM_ERR_EOF)
98 exit(0);
101 d->i = 0;
102 d->ret = krb5_storage_to_data(d->rsp, &out);
103 if (d->ret == 0)
104 d->ret = krb5_write_priv_message(d->context, d->ac, &d->fd, &out);
105 krb5_data_free(&out);
106 krb5_storage_free(d->rsp);
107 if ((d->rsp = krb5_storage_emem()) == NULL)
108 return krb5_enomem(d->context);
109 return d->ret;
112 static int
113 iter_cb(void *cbdata, const char *p)
115 struct iter_cb_data *d = cbdata;
116 krb5_error_code ret = 0;
117 size_t n = d->n;
119 /* Convince the compiler that `-(int)d->n' is defined */
120 if (n == 0 || n > INT_MAX)
121 return ERANGE;
122 if (d->rsp == NULL && (d->rsp = krb5_storage_emem()) == NULL)
123 return krb5_enomem(d->context);
124 if (d->i == 0) {
125 /* Every chunk starts with a result code */
126 ret = krb5_store_int32(d->rsp, d->ret);
127 if (ret)
128 return ret;
129 if (d->ret)
130 return ret;
132 if (d->initial) {
134 * We'll send up to `d->n' entries per-write. We send a negative
135 * number to indicate we accepted the client's proposal that we speak
136 * the online LIST protocol.
138 * Note that if we're here then we've already placed a result code in
139 * this reply (see above).
141 d->initial = 0;
142 ret = krb5_store_int32(d->rsp, -(int)n); /* Princs per-chunk */
143 if (ret == 0)
144 ret = iter_cb_send_now(d);
145 if (ret)
146 return ret;
148 * Now that we've sent the acceptance reply, put a result code as the
149 * first thing in the next reply, which will have the first chunk of
150 * the listing.
152 ret = krb5_store_int32(d->rsp, d->ret);
153 if (ret)
154 return ret;
155 if (d->ret)
156 return ret;
159 if (p) {
160 ret = krb5_store_string(d->rsp, p);
161 d->i++;
162 } else {
164 * We get called with `p == NULL' when the listing is done. This
165 * forces us to iter_cb_send_now(d) below, but also forces us to have a
166 * properly formed reply (i.e., that we have a result code as the first
167 * item), even if the chunk is otherwise empty (`d->i == 0').
169 d->i = n;
172 if (ret == 0 && d->i == n)
173 ret = iter_cb_send_now(d); /* Chunk finished; send it */
174 if (d->stop)
175 return EINTR;
176 return ret;
179 static kadm5_ret_t
180 kadmind_dispatch(void *kadm_handlep, krb5_boolean initial,
181 krb5_data *in, krb5_auth_context ac, int fd,
182 krb5_data *out, int readonly)
184 kadm5_ret_t ret = 0;
185 kadm5_ret_t ret_sp = 0;
186 int32_t cmd, mask, kvno, tmp;
187 kadm5_server_context *contextp = kadm_handlep;
188 char client[128], name[128], name2[128];
189 const char *op = "";
190 krb5_principal princ = NULL, princ2 = NULL;
191 kadm5_principal_ent_rec ent, ent_prev;
192 char *password = NULL, *expression;
193 krb5_keyblock *new_keys;
194 krb5_key_salt_tuple *ks_tuple = NULL;
195 int keepold = FALSE;
196 int n_ks_tuple = 0;
197 int n_keys;
198 char **princs;
199 int n_princs;
200 int keys_ok = 0;
201 krb5_storage *rsp; /* response goes here */
202 krb5_storage *sp;
203 int len;
205 memset(&ent, 0, sizeof(ent));
206 memset(&ent_prev, 0, sizeof(ent_prev));
207 krb5_data_zero(out);
209 rsp = krb5_storage_emem();
210 if (rsp == NULL)
211 return krb5_enomem(contextp->context);
213 sp = krb5_storage_from_data(in);
214 if (sp == NULL) {
215 krb5_storage_free(rsp);
216 return krb5_enomem(contextp->context);
219 ret = krb5_unparse_name_fixed(contextp->context, contextp->caller,
220 client, sizeof(client));
221 if (ret == 0)
222 ret = krb5_ret_int32(sp, &cmd);
223 if (ret) {
224 ret_sp = krb5_store_int32(rsp, KADM5_FAILURE);
225 goto fail;
228 switch(cmd){
229 case kadm_nop:{
231 * In the future we could use this for versioning.
233 * We used to respond to NOPs with KADM5_FAILURE. Now we respond with
234 * zero. In the future we could send back a protocol version number
235 * and use NOPs for protocol version negotiation.
237 * In the meantime, this gets called only if a client wants to
238 * interrupt a long-running LIST operation.
240 op = "NOP";
241 ret = krb5_ret_int32(sp, &tmp);
242 if (ret == 0 && tmp == 0) {
244 * Reply not wanted. This would be a LIST interrupt request.
246 krb5_storage_free(rsp);
247 krb5_storage_free(sp);
248 return 0;
250 ret_sp = krb5_store_int32(rsp, ret = 0);
251 break;
253 case kadm_get:{
254 op = "GET";
255 ret = krb5_ret_principal(sp, &princ);
256 if (ret) {
257 ret_sp = krb5_store_int32(rsp, KADM5_UNK_PRINC);
258 goto fail;
260 ret = krb5_ret_int32(sp, &mask);
261 if (ret) {
262 ret_sp = krb5_store_int32(rsp, KADM5_FAILURE);
263 goto fail;
266 mask |= KADM5_PRINCIPAL;
267 krb5_unparse_name_fixed(contextp->context, princ, name, sizeof(name));
268 krb5_warnx(contextp->context, "%s: %s %s", client, op, name);
270 /* If the caller doesn't have KADM5_PRIV_GET, we're done. */
271 ret = _kadm5_acl_check_permission(contextp, KADM5_PRIV_GET, princ);
272 if (ret) {
273 ret_sp = krb5_store_int32(rsp, ret);
274 goto fail;
277 /* Then check to see if it is ok to return keys */
278 if ((mask & KADM5_KEY_DATA) != 0) {
279 ret = _kadm5_acl_check_permission(contextp, KADM5_PRIV_GET_KEYS,
280 princ);
281 if (ret == 0) {
282 keys_ok = 1;
283 } else if ((mask == (KADM5_PRINCIPAL|KADM5_KEY_DATA)) ||
284 (mask == (KADM5_PRINCIPAL|KADM5_KVNO|KADM5_KEY_DATA))) {
286 * Requests for keys will get bogus keys, which is useful if
287 * the client just wants to see what (kvno, enctype)s the
288 * principal has keys for, but terrible if the client wants to
289 * write the keys into a keytab or modify the principal and
290 * write the bogus keys back to the server.
292 * We use a heuristic to detect which case we're handling here.
293 * If the client only asks for the flags in the above
294 * condition, then it's very likely a kadmin ext_keytab,
295 * add_enctype, or other request that should not see bogus
296 * keys. We deny them.
298 * The kadmin get command can be coaxed into making a request
299 * with the same mask. But the default long and terse output
300 * modes request other things too, so in all likelihood this
301 * heuristic will not hurt any kadmin get uses.
303 ret_sp = krb5_store_int32(rsp, ret);
304 goto fail;
308 ret = kadm5_get_principal(kadm_handlep, princ, &ent, mask);
309 ret_sp = krb5_store_int32(rsp, ret);
310 if (ret == 0) {
311 if (ret_sp == 0 && keys_ok)
312 ret_sp = kadm5_store_principal_ent(rsp, &ent);
313 else if (ret_sp == 0)
314 ret_sp = kadm5_store_principal_ent_nokeys(rsp, &ent);
316 kadm5_free_principal_ent(kadm_handlep, &ent);
317 break;
319 case kadm_delete:{
320 op = "DELETE";
321 if (readonly) {
322 ret_sp = krb5_store_int32(rsp, ret = KADM5_READ_ONLY);
323 goto fail;
325 ret = krb5_ret_principal(sp, &princ);
326 if (ret == 0)
327 ret = krb5_unparse_name_fixed(contextp->context, princ, name, sizeof(name));
328 if (ret == 0) {
329 ret = _kadm5_acl_check_permission(contextp, KADM5_PRIV_DELETE, princ);
330 krb5_warnx(contextp->context, "%s: %s %s (%s)", client, op, name,
331 ret == 0 ? "granted" : "denied");
335 * There's no need to check that the caller has permission to
336 * delete the victim principal's aliases.
338 if (ret == 0)
339 ret = kadm5_delete_principal(kadm_handlep, princ);
340 ret_sp = krb5_store_int32(rsp, ret);
341 break;
343 case kadm_create:{
344 op = "CREATE";
345 if (readonly) {
346 ret_sp = krb5_store_int32(rsp, ret = KADM5_READ_ONLY);
347 goto fail;
349 ret = kadm5_ret_principal_ent(sp, &ent);
350 if(ret) {
351 ret_sp = krb5_store_int32(rsp, ret);
352 goto fail;
354 ret = krb5_ret_int32(sp, &mask);
355 if(ret){
356 ret_sp = krb5_store_int32(rsp, KADM5_FAILURE);
357 kadm5_free_principal_ent(kadm_handlep, &ent);
358 goto fail;
360 ret = krb5_ret_string(sp, &password);
361 if(ret){
362 ret_sp = krb5_store_int32(rsp, KADM5_FAILURE);
363 kadm5_free_principal_ent(kadm_handlep, &ent);
364 goto fail;
366 krb5_unparse_name_fixed(contextp->context, ent.principal,
367 name, sizeof(name));
368 krb5_warnx(contextp->context, "%s: %s %s", client, op, name);
369 ret = _kadm5_acl_check_permission(contextp, KADM5_PRIV_ADD,
370 ent.principal);
371 if(ret){
372 ret_sp = krb5_store_int32(rsp, ret);
373 kadm5_free_principal_ent(kadm_handlep, &ent);
374 goto fail;
376 if ((mask & KADM5_TL_DATA)) {
378 * Also check that the caller can create the aliases, if the
379 * new principal has any.
381 ret = check_aliases(contextp, &ent, NULL);
382 if (ret) {
383 ret_sp = krb5_store_int32(rsp, KADM5_BAD_PRINCIPAL);
384 kadm5_free_principal_ent(kadm_handlep, &ent);
385 goto fail;
388 ret = kadm5_create_principal(kadm_handlep, &ent,
389 mask, password);
390 kadm5_free_principal_ent(kadm_handlep, &ent);
391 ret_sp = krb5_store_int32(rsp, ret);
392 break;
394 case kadm_modify:{
395 op = "MODIFY";
396 if (readonly) {
397 ret_sp = krb5_store_int32(rsp, ret = KADM5_READ_ONLY);
398 goto fail;
400 ret = kadm5_ret_principal_ent(sp, &ent);
401 if(ret) {
402 ret_sp = krb5_store_int32(rsp, KADM5_FAILURE);
403 goto fail;
405 ret = krb5_ret_int32(sp, &mask);
406 if(ret){
407 ret_sp = krb5_store_int32(rsp, KADM5_FAILURE);
408 kadm5_free_principal_ent(contextp, &ent);
409 goto fail;
411 krb5_unparse_name_fixed(contextp->context, ent.principal,
412 name, sizeof(name));
413 krb5_warnx(contextp->context, "%s: %s %s", client, op, name);
414 ret = _kadm5_acl_check_permission(contextp, KADM5_PRIV_MODIFY,
415 ent.principal);
416 if(ret){
417 ret_sp = krb5_store_int32(rsp, ret);
418 kadm5_free_principal_ent(contextp, &ent);
419 goto fail;
421 if ((mask & KADM5_TL_DATA)) {
423 * Also check that the caller can create aliases that are in
424 * the new entry but not the old one. There's no need to
425 * check that the caller can delete aliases it wants to
426 * drop. See also handling of rename.
428 ret = kadm5_get_principal(kadm_handlep, ent.principal, &ent_prev, mask);
429 if (ret) {
430 ret_sp = krb5_store_int32(rsp, ret);
431 kadm5_free_principal_ent(contextp, &ent);
432 goto fail;
434 ret = check_aliases(contextp, &ent, &ent_prev);
435 kadm5_free_principal_ent(contextp, &ent_prev);
436 if (ret) {
437 ret_sp = krb5_store_int32(rsp, KADM5_BAD_PRINCIPAL);
438 kadm5_free_principal_ent(contextp, &ent);
439 goto fail;
442 ret = kadm5_modify_principal(kadm_handlep, &ent, mask);
443 kadm5_free_principal_ent(kadm_handlep, &ent);
444 ret_sp = krb5_store_int32(rsp, ret);
445 break;
447 case kadm_prune:{
448 op = "PRUNE";
449 if (readonly) {
450 ret_sp = krb5_store_int32(rsp, ret = KADM5_READ_ONLY);
451 goto fail;
453 ret = krb5_ret_principal(sp, &princ);
454 if (ret == 0)
455 ret = krb5_ret_int32(sp, &kvno);
456 if (ret == HEIM_ERR_EOF) {
457 kvno = 0;
458 } else if (ret) {
459 ret_sp = krb5_store_int32(rsp, KADM5_FAILURE);
460 goto fail;
462 krb5_unparse_name_fixed(contextp->context, princ, name, sizeof(name));
463 krb5_warnx(contextp->context, "%s: %s %s", client, op, name);
464 ret = _kadm5_acl_check_permission(contextp, KADM5_PRIV_CPW, princ);
465 if (ret) {
466 ret_sp = krb5_store_int32(rsp, ret);
467 goto fail;
470 ret = kadm5_prune_principal(kadm_handlep, princ, kvno);
471 ret_sp = krb5_store_int32(rsp, ret);
472 break;
474 case kadm_rename:{
475 op = "RENAME";
476 if (readonly) {
477 ret_sp = krb5_store_int32(rsp, ret = KADM5_READ_ONLY);
478 goto fail;
480 ret = krb5_ret_principal(sp, &princ);
481 if (ret == 0)
482 ret = krb5_ret_principal(sp, &princ2);
483 if (ret) {
484 ret_sp = krb5_store_int32(rsp, KADM5_FAILURE);
485 goto fail;
488 krb5_unparse_name_fixed(contextp->context, princ, name, sizeof(name));
489 krb5_unparse_name_fixed(contextp->context, princ2,
490 name2, sizeof(name2));
491 krb5_warnx(contextp->context, "%s: %s %s -> %s",
492 client, op, name, name2);
493 ret = _kadm5_acl_check_permission(contextp,
494 KADM5_PRIV_ADD,
495 princ2);
496 if (ret == 0) {
498 * Also require modify for the principal. For backwards
499 * compatibility, allow delete permission on the old name to
500 * cure lack of modify permission on the old name.
502 ret = _kadm5_acl_check_permission(contextp,
503 KADM5_PRIV_MODIFY,
504 princ);
505 if (ret) {
506 ret = _kadm5_acl_check_permission(contextp,
507 KADM5_PRIV_DELETE,
508 princ);
511 if (ret) {
512 ret_sp = krb5_store_int32(rsp, ret);
513 goto fail;
516 ret = kadm5_rename_principal(kadm_handlep, princ, princ2);
517 ret_sp = krb5_store_int32(rsp, ret);
518 break;
520 case kadm_chpass:{
521 krb5_boolean is_self_cpw, allow_self_cpw;
523 op = "CHPASS";
524 if (readonly) {
525 ret_sp = krb5_store_int32(rsp, ret = KADM5_READ_ONLY);
526 goto fail;
528 ret = krb5_ret_principal(sp, &princ);
529 if (ret == 0)
530 ret = krb5_ret_string(sp, &password);
531 if (ret == 0)
532 ret = krb5_ret_int32(sp, &keepold);
533 if (ret == HEIM_ERR_EOF)
534 ret = 0;
535 if (ret == 0) {
536 ret = krb5_unparse_name_fixed(contextp->context, princ, name, sizeof(name));
537 if (ret == 0)
538 krb5_warnx(contextp->context, "%s: %s %s", client, op, name);
540 if (ret) {
541 ret_sp = krb5_store_int32(rsp, KADM5_FAILURE);
542 goto fail;
546 * Change password requests are subject to ACLs unless the principal is
547 * changing their own password and the initial ticket flag is set, and
548 * the allow_self_change_password configuration option is TRUE.
550 is_self_cpw =
551 krb5_principal_compare(contextp->context, contextp->caller, princ);
552 allow_self_cpw =
553 krb5_config_get_bool_default(contextp->context, NULL, TRUE,
554 "kadmin", "allow_self_change_password", NULL);
555 if (!(is_self_cpw && initial && allow_self_cpw)) {
556 ret = _kadm5_acl_check_permission(contextp, KADM5_PRIV_CPW, princ);
557 if (ret) {
558 ret_sp = krb5_store_int32(rsp, ret);
559 goto fail;
563 ret = kadm5_chpass_principal_3(kadm_handlep, princ, keepold, 0, NULL,
564 password);
565 ret_sp = krb5_store_int32(rsp, ret);
566 break;
568 case kadm_chpass_with_key:{
569 int i;
570 krb5_key_data *key_data;
571 int n_key_data;
573 op = "CHPASS_WITH_KEY";
574 if (readonly) {
575 ret_sp = krb5_store_int32(rsp, ret = KADM5_READ_ONLY);
576 goto fail;
578 ret = krb5_ret_principal(sp, &princ);
579 if (ret == 0)
580 ret = krb5_ret_int32(sp, &n_key_data);
581 if (ret == 0) {
582 ret = krb5_ret_int32(sp, &keepold);
583 if (ret == HEIM_ERR_EOF)
584 ret = 0;
586 if (ret) {
587 ret_sp = krb5_store_int32(rsp, KADM5_FAILURE);
588 goto fail;
591 /* n_key_data will be squeezed into an int16_t below. */
592 if (n_key_data < 0 || n_key_data >= 1 << 16 ||
593 (size_t)n_key_data > UINT_MAX/sizeof(*key_data)) {
594 ret_sp = krb5_store_int32(rsp, KADM5_FAILURE);
595 ret = ERANGE;
596 goto fail;
599 key_data = malloc (n_key_data * sizeof(*key_data));
600 if (key_data == NULL && n_key_data != 0) {
601 ret_sp = krb5_store_int32(rsp, KADM5_FAILURE);
602 ret = krb5_enomem(contextp->context);
603 goto fail;
606 for (i = 0; i < n_key_data; ++i) {
607 ret = kadm5_ret_key_data (sp, &key_data[i]);
608 if (ret) {
609 int16_t dummy = i;
611 kadm5_free_key_data (contextp, &dummy, key_data);
612 free (key_data);
613 ret_sp = krb5_store_int32(rsp, KADM5_FAILURE);
614 goto fail;
619 * The change is only allowed if the user is on the CPW ACL,
620 * this it to force password quality check on the user.
623 ret = _kadm5_acl_check_permission(contextp, KADM5_PRIV_CPW, princ);
624 ret_sp = krb5_unparse_name_fixed(contextp->context, princ, name, sizeof(name));
625 if (ret_sp == 0)
626 krb5_warnx(contextp->context, "%s: %s %s (%s)", client, op, name,
627 ret ? "denied" : "granted");
628 if(ret) {
629 int16_t dummy = n_key_data;
631 kadm5_free_key_data (contextp, &dummy, key_data);
632 free (key_data);
633 ret_sp = krb5_store_int32(rsp, KADM5_FAILURE);
634 goto fail;
636 ret = kadm5_chpass_principal_with_key_3(kadm_handlep, princ, keepold,
637 n_key_data, key_data);
639 int16_t dummy = n_key_data;
640 kadm5_free_key_data (contextp, &dummy, key_data);
642 free (key_data);
643 ret_sp = krb5_store_int32(rsp, ret);
644 break;
646 case kadm_randkey:{
647 size_t i;
649 op = "RANDKEY";
650 if (readonly) {
651 ret_sp = krb5_store_int32(rsp, ret = KADM5_READ_ONLY);
652 goto fail;
654 ret = krb5_ret_principal(sp, &princ);
655 if (ret) {
656 ret_sp = krb5_store_int32(rsp, KADM5_FAILURE);
657 goto fail;
659 krb5_unparse_name_fixed(contextp->context, princ, name, sizeof(name));
660 krb5_warnx(contextp->context, "%s: %s %s", client, op, name);
662 * The change is allowed if at least one of:
663 * a) it's for the principal him/herself and this was an initial ticket
664 * b) the user is on the CPW ACL.
667 if (initial
668 && krb5_principal_compare (contextp->context, contextp->caller,
669 princ))
670 ret = 0;
671 else
672 ret = _kadm5_acl_check_permission(contextp, KADM5_PRIV_CPW, princ);
674 if (ret) {
675 ret_sp = krb5_store_int32(rsp, ret);
676 goto fail;
680 * See comments in kadm5_c_randkey_principal() regarding the
681 * protocol.
683 ret = krb5_ret_int32(sp, &keepold);
684 if (ret != 0 && ret != HEIM_ERR_EOF) {
685 ret_sp = krb5_store_int32(rsp, KADM5_FAILURE);
686 goto fail;
689 ret = krb5_ret_int32(sp, &n_ks_tuple);
690 if (ret == HEIM_ERR_EOF) {
691 const char *enctypes;
692 size_t n;
694 enctypes = krb5_config_get_string(contextp->context, NULL,
695 "realms",
696 krb5_principal_get_realm(contextp->context,
697 princ),
698 "supported_enctypes", NULL);
699 if (enctypes == NULL || enctypes[0] == '\0')
700 enctypes = "aes128-cts-hmac-sha1-96";
701 ret = krb5_string_to_keysalts2(contextp->context, enctypes,
702 &n, &ks_tuple);
703 n_ks_tuple = n;
705 if (ret != 0) {
706 ret_sp = krb5_store_int32(rsp, KADM5_FAILURE); /* XXX */
707 goto fail;
710 if (n_ks_tuple < 0) {
711 ret_sp = krb5_store_int32(rsp, KADM5_FAILURE); /* XXX */
712 ret = EOVERFLOW;
713 goto fail;
715 free(ks_tuple);
716 if ((ks_tuple = calloc(n_ks_tuple, sizeof (*ks_tuple))) == NULL) {
717 ret_sp = krb5_store_int32(rsp, KADM5_FAILURE);
718 ret = errno;
719 goto fail;
722 for (i = 0; i < n_ks_tuple; i++) {
723 ret = krb5_ret_int32(sp, &ks_tuple[i].ks_enctype);
724 if (ret != 0) {
725 ret_sp = krb5_store_int32(rsp, KADM5_FAILURE);
726 free(ks_tuple);
727 goto fail;
729 ret = krb5_ret_int32(sp, &ks_tuple[i].ks_salttype);
730 if (ret != 0) {
731 ret_sp = krb5_store_int32(rsp, KADM5_FAILURE);
732 free(ks_tuple);
733 goto fail;
736 ret = kadm5_randkey_principal_3(kadm_handlep, princ, keepold,
737 n_ks_tuple, ks_tuple, &new_keys,
738 &n_keys);
739 free(ks_tuple);
741 ret_sp = krb5_store_int32(rsp, ret);
742 if (ret == 0 && ret_sp == 0){
743 ret_sp = krb5_store_int32(rsp, n_keys);
744 for (i = 0; i < n_keys; i++){
745 if (ret_sp == 0)
746 ret_sp = krb5_store_keyblock(rsp, new_keys[i]);
747 krb5_free_keyblock_contents(contextp->context, &new_keys[i]);
749 free(new_keys);
751 break;
753 case kadm_get_privs:{
754 uint32_t privs;
755 ret = kadm5_get_privs(kadm_handlep, &privs);
756 if (ret == 0)
757 ret_sp = krb5_store_uint32(rsp, privs);
758 break;
760 case kadm_get_princs:{
761 op = "LIST";
762 ret = krb5_ret_int32(sp, &tmp);
763 if (ret) {
764 ret_sp = krb5_store_int32(rsp, KADM5_FAILURE);
765 goto fail;
767 /* See kadm5_c_iter_principals() */
768 if (tmp == 0x55555555) {
769 /* Want online iteration */
770 ret = krb5_ret_string(sp, &expression);
771 if (ret) {
772 ret_sp = krb5_store_int32(rsp, KADM5_FAILURE);
773 goto fail;
775 if (expression[0] == '\0') {
776 free(expression);
777 expression = NULL;
779 } else if (tmp) {
780 ret = krb5_ret_string(sp, &expression);
781 if (ret) {
782 ret_sp = krb5_store_int32(rsp, KADM5_FAILURE);
783 goto fail;
785 }else
786 expression = NULL;
787 krb5_warnx(contextp->context, "%s: %s %s", client, op,
788 expression ? expression : "*");
789 ret = _kadm5_acl_check_permission(contextp, KADM5_PRIV_LIST, NULL);
790 if(ret){
791 ret_sp = krb5_store_int32(rsp, ret);
792 free(expression);
793 goto fail;
795 if (fd > -1 && tmp == 0x55555555) {
796 struct iter_cb_data iter_cbdata;
797 int n;
800 * The client proposes that we speak the online variation of LIST
801 * by sending a magic value in the int32 that is meant to be a
802 * boolean for "an expression follows". The client must send an
803 * expression in this case because the server might be an old one,
804 * so even if the caller to kadm5_get/iter_principals() passed NULL
805 * for the expression, the client must send something ("*").
807 * The list of principals will be streamed in multiple replies.
809 * The first reply will have just a return code and a negative
810 * count of maximum number of names per-subsequent reply. See
811 * `iter_cb()'.
813 * The second reply, third, .., nth replies will have a return code
814 * followed by 50 names, except the last reply must have fewer than
815 * 50 names -zero if need be- so the client can deterministically
816 * notice the end of the stream.
819 n = list_chunk_size;
820 if (n < 0)
821 n = krb5_config_get_int_default(contextp->context, NULL, -1,
822 "kadmin", "list_chunk_size", NULL);
823 if (n < 0)
824 n = 50;
825 if (n > 500)
826 n = 500;
827 if ((iter_cbdata.rsp = krb5_storage_emem()) == NULL) {
828 ret_sp = krb5_store_int32(rsp, KADM5_FAILURE);
829 ret = krb5_enomem(contextp->context);
830 goto fail;
832 iter_cbdata.context = contextp->context;
833 iter_cbdata.initial = 1;
834 iter_cbdata.stop = 0;
835 iter_cbdata.ret = 0;
836 iter_cbdata.ac = ac;
837 iter_cbdata.fd = fd;
838 iter_cbdata.n = n;
839 iter_cbdata.i = 0;
842 * All sending of replies will happen in iter_cb, except for the
843 * final chunk with the final result code.
845 iter_cbdata.ret = kadm5_iter_principals(kadm_handlep, expression,
846 iter_cb, &iter_cbdata);
847 /* Send terminating chunk */
848 iter_cb(&iter_cbdata, NULL);
849 /* Final result */
850 ret = krb5_store_int32(rsp, iter_cbdata.ret);
851 krb5_storage_free(iter_cbdata.rsp);
852 } else {
853 ret = kadm5_get_principals(kadm_handlep, expression, &princs, &n_princs);
854 ret_sp = krb5_store_int32(rsp, ret);
855 if (ret == 0 && ret_sp == 0) {
856 int i;
858 ret_sp = krb5_store_int32(rsp, n_princs);
859 for (i = 0; ret_sp == 0 && i < n_princs; i++)
860 ret_sp = krb5_store_string(rsp, princs[i]);
861 kadm5_free_name_list(kadm_handlep, princs, &n_princs);
864 free(expression);
865 break;
867 default:
868 krb5_warnx(contextp->context, "%s: UNKNOWN OP %d", client, cmd);
869 ret_sp = krb5_store_int32(rsp, KADM5_FAILURE);
870 break;
873 fail:
874 if (password != NULL) {
875 len = strlen(password);
876 memset_s(password, len, 0, len);
877 free(password);
879 krb5_storage_to_data(rsp, out);
880 krb5_storage_free(rsp);
881 krb5_storage_free(sp);
882 krb5_free_principal(contextp->context, princ);
883 krb5_free_principal(contextp->context, princ2);
884 if (ret)
885 krb5_warn(contextp->context, ret, "%s", op);
886 if (out->length == 0)
887 krb5_warn(contextp->context, ret, "%s: reply failed", op);
888 else if (ret_sp)
889 krb5_warn(contextp->context, ret, "%s: reply incomplete", op);
890 if (ret_sp)
891 return ret_sp;
892 return 0;
895 struct iter_aliases_ctx {
896 HDB_Ext_Aliases aliases;
897 krb5_tl_data *tl;
898 int alias_idx;
899 int done;
902 static kadm5_ret_t
903 iter_aliases(kadm5_principal_ent_rec *from,
904 struct iter_aliases_ctx *ctx,
905 krb5_principal *out)
907 HDB_extension ext;
908 kadm5_ret_t ret;
909 size_t size;
911 *out = NULL;
913 if (ctx->done > 0)
914 return 0;
915 if (from == NULL) {
916 ctx->done = 1;
917 return 0;
920 if (ctx->done == 0) {
921 if (ctx->alias_idx < ctx->aliases.aliases.len) {
922 *out = &ctx->aliases.aliases.val[ctx->alias_idx++];
923 return 0;
925 /* Out of aliases in this TL, step to next TL */
926 ctx->tl = ctx->tl->tl_data_next;
927 } else if (ctx->done < 0) {
928 /* Setup iteration context */
929 memset(ctx, 0, sizeof(*ctx));
930 ctx->done = 0;
931 ctx->aliases.aliases.val = NULL;
932 ctx->aliases.aliases.len = 0;
933 ctx->tl = from->tl_data;
936 free_HDB_Ext_Aliases(&ctx->aliases);
937 ctx->alias_idx = 0;
939 /* Find TL with aliases */
940 for (; ctx->tl != NULL; ctx->tl = ctx->tl->tl_data_next) {
941 if (ctx->tl->tl_data_type != KRB5_TL_EXTENSION)
942 continue;
944 ret = decode_HDB_extension(ctx->tl->tl_data_contents,
945 ctx->tl->tl_data_length,
946 &ext, &size);
947 if (ret)
948 return ret;
949 if (ext.data.element == choice_HDB_extension_data_aliases &&
950 ext.data.u.aliases.aliases.len > 0) {
951 ctx->aliases = ext.data.u.aliases;
952 break;
954 free_HDB_extension(&ext);
957 if (ctx->tl != NULL && ctx->aliases.aliases.len > 0) {
958 *out = &ctx->aliases.aliases.val[ctx->alias_idx++];
959 return 0;
962 ctx->done = 1;
963 return 0;
966 static kadm5_ret_t
967 check_aliases(kadm5_server_context *contextp,
968 kadm5_principal_ent_rec *add_princ,
969 kadm5_principal_ent_rec *del_princ)
971 kadm5_ret_t ret;
972 struct iter_aliases_ctx iter;
973 struct iter_aliases_ctx iter_del;
974 krb5_principal new_name, old_name;
975 int match;
978 * Yeah, this is O(N^2). Gathering and sorting all the aliases
979 * would be a bit of a pain; if we ever have principals with enough
980 * aliases for this to be a problem, we can fix it then.
982 for (iter.done = -1; iter.done != 1;) {
983 match = 0;
984 ret = iter_aliases(add_princ, &iter, &new_name);
985 if (ret)
986 return ret;
987 if (iter.done == 1)
988 break;
989 for (iter_del.done = -1; iter_del.done != 1;) {
990 ret = iter_aliases(del_princ, &iter_del, &old_name);
991 if (ret)
992 return ret;
993 if (iter_del.done == 1)
994 break;
995 if (!krb5_principal_compare(contextp->context, new_name, old_name))
996 continue;
997 free_HDB_Ext_Aliases(&iter_del.aliases);
998 match = 1;
999 break;
1001 if (match)
1002 continue;
1003 ret = _kadm5_acl_check_permission(contextp, KADM5_PRIV_ADD, new_name);
1004 if (ret) {
1005 free_HDB_Ext_Aliases(&iter.aliases);
1006 return ret;
1010 return 0;
1013 static void
1014 v5_loop (krb5_context contextp,
1015 krb5_auth_context ac,
1016 krb5_boolean initial,
1017 void *kadm_handlep,
1018 krb5_socket_t fd,
1019 int readonly)
1021 krb5_error_code ret;
1022 krb5_data in, out;
1024 for (;;) {
1025 doing_useful_work = 0;
1026 if(term_flag)
1027 exit(0);
1028 ret = krb5_read_priv_message(contextp, ac, &fd, &in);
1029 if(ret == HEIM_ERR_EOF)
1030 exit(0);
1031 if(ret)
1032 krb5_err(contextp, 1, ret, "krb5_read_priv_message");
1033 doing_useful_work = 1;
1034 ret = kadmind_dispatch(kadm_handlep, initial, &in, ac, fd, &out,
1035 readonly);
1036 if (ret)
1037 krb5_err(contextp, 1, ret, "kadmind_dispatch");
1038 krb5_data_free(&in);
1039 if (out.length)
1040 ret = krb5_write_priv_message(contextp, ac, &fd, &out);
1041 krb5_data_free(&out);
1042 if(ret)
1043 krb5_err(contextp, 1, ret, "krb5_write_priv_message");
1047 static krb5_boolean
1048 match_appl_version(const void *data, const char *appl_version)
1050 unsigned minor;
1051 if(sscanf(appl_version, "KADM0.%u", &minor) != 1)
1052 return 0;
1053 /*XXX*/
1054 *(unsigned*)(intptr_t)data = minor;
1055 return 1;
1058 static void
1059 handle_v5(krb5_context contextp,
1060 krb5_keytab keytab,
1061 krb5_socket_t fd,
1062 int readonly)
1064 krb5_error_code ret;
1065 krb5_ticket *ticket;
1066 char *server_name;
1067 char *client;
1068 void *kadm_handlep;
1069 krb5_boolean initial;
1070 krb5_auth_context ac = NULL;
1071 unsigned kadm_version = 1;
1072 kadm5_config_params realm_params;
1074 ret = krb5_recvauth_match_version(contextp, &ac, &fd,
1075 match_appl_version, &kadm_version,
1076 NULL, KRB5_RECVAUTH_IGNORE_VERSION,
1077 keytab, &ticket);
1078 if (ret) {
1079 krb5_err(contextp, 1, ret, "krb5_recvauth");
1080 return;
1082 ret = krb5_unparse_name(contextp, ticket->server, &server_name);
1083 if (ret) {
1084 krb5_err(contextp, 1, ret, "krb5_unparse_name");
1085 krb5_free_ticket(contextp, ticket);
1086 return;
1088 if (strncmp(server_name, KADM5_ADMIN_SERVICE,
1089 strlen(KADM5_ADMIN_SERVICE)) != 0) {
1090 krb5_errx(contextp, 1, "ticket for strange principal (%s)", server_name);
1091 krb5_free_ticket(contextp, ticket);
1092 free(server_name);
1093 return;
1095 free(server_name);
1097 memset(&realm_params, 0, sizeof(realm_params));
1099 if(kadm_version == 1) {
1100 krb5_data params;
1101 ret = krb5_read_priv_message(contextp, ac, &fd, &params);
1102 if (ret) {
1103 krb5_err(contextp, 1, ret, "krb5_read_priv_message");
1104 krb5_free_ticket(contextp, ticket);
1105 return;
1107 ret = _kadm5_unmarshal_params(contextp, &params, &realm_params);
1108 if (ret) {
1109 krb5_err(contextp, 1, ret,
1110 "Could not read or parse kadm5 parameters");
1111 krb5_free_ticket(contextp, ticket);
1112 return;
1116 initial = ticket->ticket.flags.initial;
1117 ret = krb5_unparse_name(contextp, ticket->client, &client);
1118 krb5_free_ticket(contextp, ticket);
1119 if (ret) {
1120 krb5_err(contextp, 1, ret, "krb5_unparse_name");
1121 return;
1123 ret = kadm5_s_init_with_password_ctx(contextp,
1124 client,
1125 NULL,
1126 KADM5_ADMIN_SERVICE,
1127 &realm_params,
1128 0, 0,
1129 &kadm_handlep);
1130 if (ret) {
1131 krb5_err(contextp, 1, ret, "kadm5_init_with_password_ctx");
1132 return;
1134 v5_loop(contextp, ac, initial, kadm_handlep, fd, readonly);
1137 krb5_error_code
1138 kadmind_loop(krb5_context contextp,
1139 krb5_keytab keytab,
1140 krb5_socket_t sock,
1141 int readonly)
1143 u_char buf[sizeof(KRB5_SENDAUTH_VERSION) + 4];
1144 ssize_t n;
1145 unsigned long len;
1147 n = krb5_net_read(contextp, &sock, buf, 4);
1148 if(n == 0)
1149 exit(0);
1150 if(n < 0)
1151 krb5_err(contextp, 1, errno, "read");
1152 _krb5_get_int(buf, &len, 4);
1154 if (len == sizeof(KRB5_SENDAUTH_VERSION)) {
1156 n = krb5_net_read(contextp, &sock, buf + 4, len);
1157 if (n < 0)
1158 krb5_err (contextp, 1, errno, "reading sendauth version");
1159 if (n == 0)
1160 krb5_errx (contextp, 1, "EOF reading sendauth version");
1162 if(memcmp(buf + 4, KRB5_SENDAUTH_VERSION, len) == 0) {
1163 handle_v5(contextp, keytab, sock, readonly);
1164 return 0;
1166 len += 4;
1167 } else
1168 len = 4;
1170 handle_mit(contextp, buf, len, sock, readonly);
1172 return 0;