xref: /freebsd/crypto/heimdal/kadmin/server.c (revision 2a63c3be158216222d89a073dcbd6a72ee4aab5a)
1 /*
2  * Copyright (c) 1997 - 2005 Kungliga Tekniska Högskolan
3  * (Royal Institute of Technology, Stockholm, Sweden).
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  *
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  *
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.
16  *
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.
20  *
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.
32  */
33 
34 #include "kadmin_locl.h"
35 #include <krb5-private.h>
36 
37 static kadm5_ret_t
38 kadmind_dispatch(void *kadm_handlep, krb5_boolean initial,
39 		 krb5_data *in, krb5_data *out)
40 {
41     kadm5_ret_t ret = 0;
42     kadm5_ret_t ret_sp = 0;
43     int32_t cmd, mask, tmp;
44     kadm5_server_context *contextp = kadm_handlep;
45     char client[128], name[128], name2[128];
46     const char *op = "";
47     krb5_principal princ, princ2;
48     kadm5_principal_ent_rec ent;
49     char *password, *expression;
50     krb5_keyblock *new_keys;
51     int n_keys;
52     char **princs;
53     int n_princs;
54     krb5_storage *rsp = NULL; /* response goes here */
55     krb5_storage *sp = NULL;
56 
57     memset(&ent, 0, sizeof(ent));
58     krb5_data_zero(out);
59     ret = krb5_unparse_name_fixed(contextp->context, contextp->caller,
60 			    client, sizeof(client));
61 
62     sp = krb5_storage_from_data(in);
63     if (sp == NULL)
64 	krb5_errx(contextp->context, 1, "out of memory");
65 
66     ret = krb5_ret_int32(sp, &cmd);
67     if (ret) {
68 	krb5_storage_free(sp);
69 	goto fail;
70     }
71     switch(cmd){
72     case kadm_get:{
73 	op = "GET";
74 	ret = krb5_ret_principal(sp, &princ);
75 	if(ret)
76 	    goto fail;
77 	ret = krb5_ret_int32(sp, &mask);
78 	if(ret){
79 	    krb5_free_principal(contextp->context, princ);
80 	    goto fail;
81 	}
82 	mask |= KADM5_PRINCIPAL;
83 	ret = krb5_unparse_name_fixed(contextp->context, princ, name, sizeof(name));
84 	krb5_warnx(contextp->context, "%s: %s %s", client, op, name);
85 	if (ret == 0)
86 	    ret = _kadm5_acl_check_permission(contextp, KADM5_PRIV_GET, princ);
87 	if(ret){
88 	    krb5_free_principal(contextp->context, princ);
89 	    goto fail;
90 	}
91 	ret = kadm5_get_principal(kadm_handlep, princ, &ent, mask);
92 	krb5_storage_free(sp);
93 	sp = krb5_storage_emem();
94 	krb5_store_int32(sp, ret);
95 	if(ret == 0){
96 	    kadm5_store_principal_ent(sp, &ent);
97 	    kadm5_free_principal_ent(kadm_handlep, &ent);
98 	}
99 	krb5_free_principal(contextp->context, princ);
100 	break;
101     }
102     case kadm_delete:{
103 	op = "DELETE";
104 	ret = krb5_ret_principal(sp, &princ);
105 	if(ret)
106 	    goto fail;
107 	ret = krb5_unparse_name_fixed(contextp->context, princ, name, sizeof(name));
108 	krb5_warnx(contextp->context, "%s: %s %s", client, op, name);
109 	if (ret == 0)
110 	    ret = _kadm5_acl_check_permission(contextp, KADM5_PRIV_DELETE, princ);
111 	if(ret){
112 	    krb5_free_principal(contextp->context, princ);
113 	    goto fail;
114 	}
115 	ret = kadm5_delete_principal(kadm_handlep, princ);
116 	krb5_free_principal(contextp->context, princ);
117 	krb5_storage_free(sp);
118 	sp = krb5_storage_emem();
119 	krb5_store_int32(sp, ret);
120 	break;
121     }
122     case kadm_create:{
123 	op = "CREATE";
124 	ret = kadm5_ret_principal_ent(sp, &ent);
125 	if(ret)
126 	    goto fail;
127 	ret = krb5_ret_int32(sp, &mask);
128 	if(ret){
129 	    kadm5_free_principal_ent(contextp->context, &ent);
130 	    goto fail;
131 	}
132 	ret = krb5_ret_string(sp, &password);
133 	if(ret){
134 	    kadm5_free_principal_ent(contextp->context, &ent);
135 	    goto fail;
136 	}
137 	ret = krb5_unparse_name_fixed(contextp->context, ent.principal,
138 				name, sizeof(name));
139 	krb5_warnx(contextp->context, "%s: %s %s", client, op, name);
140 	if (ret == 0)
141 	    ret = _kadm5_acl_check_permission(contextp, KADM5_PRIV_ADD,
142 					  ent.principal);
143 	if(ret){
144 	    kadm5_free_principal_ent(contextp->context, &ent);
145 	    memset(password, 0, strlen(password));
146 	    free(password);
147 	    goto fail;
148 	}
149 	ret = kadm5_create_principal(kadm_handlep, &ent,
150 				     mask, password);
151 	kadm5_free_principal_ent(kadm_handlep, &ent);
152 	memset(password, 0, strlen(password));
153 	free(password);
154 	krb5_storage_free(sp);
155 	sp = krb5_storage_emem();
156 	krb5_store_int32(sp, ret);
157 	break;
158     }
159     case kadm_modify:{
160 	op = "MODIFY";
161 	ret = kadm5_ret_principal_ent(sp, &ent);
162 	if(ret)
163 	    goto fail;
164 	ret = krb5_ret_int32(sp, &mask);
165 	if(ret){
166 	    kadm5_free_principal_ent(contextp, &ent);
167 	    goto fail;
168 	}
169 	ret = krb5_unparse_name_fixed(contextp->context, ent.principal,
170 				name, sizeof(name));
171 	krb5_warnx(contextp->context, "%s: %s %s", client, op, name);
172 	if (ret == 0)
173 	    ret = _kadm5_acl_check_permission(contextp, KADM5_PRIV_MODIFY,
174 					  ent.principal);
175 	if(ret){
176 	    kadm5_free_principal_ent(contextp, &ent);
177 	    goto fail;
178 	}
179 	ret = kadm5_modify_principal(kadm_handlep, &ent, mask);
180 	kadm5_free_principal_ent(kadm_handlep, &ent);
181 	krb5_storage_free(sp);
182 	sp = krb5_storage_emem();
183 	krb5_store_int32(sp, ret);
184 	break;
185     }
186     case kadm_rename:{
187 	op = "RENAME";
188 	ret = krb5_ret_principal(sp, &princ);
189 	if(ret)
190 	    goto fail;
191 	ret = krb5_ret_principal(sp, &princ2);
192 	if(ret){
193 	    krb5_free_principal(contextp->context, princ);
194 	    goto fail;
195 	}
196 	ret = krb5_unparse_name_fixed(contextp->context, princ, name, sizeof(name));
197 	if (ret == 0)
198 	    ret = krb5_unparse_name_fixed(contextp->context, princ2, name2, sizeof(name2));
199 	krb5_warnx(contextp->context, "%s: %s %s -> %s",
200 		   client, op, name, name2);
201 	if (ret == 0)
202 	    ret = _kadm5_acl_check_permission(contextp,
203 					  KADM5_PRIV_ADD,
204 					  princ2)
205 	    || _kadm5_acl_check_permission(contextp,
206 					   KADM5_PRIV_DELETE,
207 					   princ);
208 	if(ret){
209 	    krb5_free_principal(contextp->context, princ);
210 	    krb5_free_principal(contextp->context, princ2);
211 	    goto fail;
212 	}
213 	ret = kadm5_rename_principal(kadm_handlep, princ, princ2);
214 	krb5_free_principal(contextp->context, princ);
215 	krb5_free_principal(contextp->context, princ2);
216 	krb5_storage_free(sp);
217 	sp = krb5_storage_emem();
218 	krb5_store_int32(sp, ret);
219 	break;
220     }
221     case kadm_chpass:{
222 	op = "CHPASS";
223 	ret = krb5_ret_principal(sp, &princ);
224 	if(ret)
225 	    goto fail;
226 	ret = krb5_ret_string(sp, &password);
227 	if(ret){
228 	    krb5_free_principal(contextp->context, princ);
229 	    goto fail;
230 	}
231 	ret = krb5_unparse_name_fixed(contextp->context, princ, name, sizeof(name));
232 	krb5_warnx(contextp->context, "%s: %s %s", client, op, name);
233 
234 	/*
235 	 * The change is allowed if at least one of:
236 	 *
237 	 * a) allowed by sysadmin
238 	 * b) it's for the principal him/herself and this was an
239 	 *    initial ticket, but then, check with the password quality
240 	 *    function.
241 	 * c) the user is on the CPW ACL.
242 	 */
243 
244 	if (ret == 0) {
245 	    if (krb5_config_get_bool_default(contextp->context, NULL, TRUE,
246 						 "kadmin", "allow_self_change_password", NULL)
247 		&& initial
248 		&& krb5_principal_compare (contextp->context, contextp->caller,
249 					       princ))
250 		{
251 		    krb5_data pwd_data;
252 		    const char *pwd_reason;
253 
254 		    pwd_data.data = password;
255 		    pwd_data.length = strlen(password);
256 
257 		    pwd_reason = kadm5_check_password_quality (contextp->context,
258 							       princ, &pwd_data);
259 		    if (pwd_reason != NULL)
260 			ret = KADM5_PASS_Q_DICT;
261 		    else
262 			ret = 0;
263 		} else
264 		    ret = _kadm5_acl_check_permission(contextp, KADM5_PRIV_CPW, princ);
265 	}
266 
267 	if(ret) {
268 	    krb5_free_principal(contextp->context, princ);
269 	    memset(password, 0, strlen(password));
270 	    free(password);
271 	    goto fail;
272 	}
273 	ret = kadm5_chpass_principal(kadm_handlep, princ, password);
274 	krb5_free_principal(contextp->context, princ);
275 	memset(password, 0, strlen(password));
276 	free(password);
277 	krb5_storage_free(sp);
278 	sp = krb5_storage_emem();
279 	krb5_store_int32(sp, ret);
280 	break;
281     }
282     case kadm_chpass_with_key:{
283 	int i;
284 	krb5_key_data *key_data;
285 	int n_key_data;
286 
287 	op = "CHPASS_WITH_KEY";
288 	ret = krb5_ret_principal(sp, &princ);
289 	if(ret)
290 	    goto fail;
291 	ret = krb5_ret_int32(sp, &n_key_data);
292 	if (ret) {
293 	    krb5_free_principal(contextp->context, princ);
294 	    goto fail;
295 	}
296 	/* n_key_data will be squeezed into an int16_t below. */
297 	if (n_key_data < 0 || n_key_data >= 1 << 16 ||
298 	    (size_t)n_key_data > UINT_MAX/sizeof(*key_data)) {
299 	    ret = ERANGE;
300 	    krb5_free_principal(contextp->context, princ);
301 	    goto fail;
302 	}
303 
304 	key_data = malloc (n_key_data * sizeof(*key_data));
305 	if (key_data == NULL && n_key_data != 0) {
306 	    ret = ENOMEM;
307 	    krb5_free_principal(contextp->context, princ);
308 	    goto fail;
309 	}
310 
311 	for (i = 0; i < n_key_data; ++i) {
312 	    ret = kadm5_ret_key_data (sp, &key_data[i]);
313 	    if (ret) {
314 		int16_t dummy = i;
315 
316 		kadm5_free_key_data (contextp, &dummy, key_data);
317 		free (key_data);
318 		krb5_free_principal(contextp->context, princ);
319 		goto fail;
320 	    }
321 	}
322 
323 	ret = krb5_unparse_name_fixed(contextp->context, princ, name, sizeof(name));
324 	krb5_warnx(contextp->context, "%s: %s %s", client, op, name);
325 
326 	/*
327 	 * The change is only allowed if the user is on the CPW ACL,
328 	 * this it to force password quality check on the user.
329 	 */
330 
331 	if (ret == 0)
332 	    ret = _kadm5_acl_check_permission(contextp, KADM5_PRIV_CPW, princ);
333 	if(ret) {
334 	    int16_t dummy = n_key_data;
335 
336 	    kadm5_free_key_data (contextp, &dummy, key_data);
337 	    free (key_data);
338 	    krb5_free_principal(contextp->context, princ);
339 	    goto fail;
340 	}
341 	ret = kadm5_chpass_principal_with_key(kadm_handlep, princ,
342 					      n_key_data, key_data);
343 	{
344 	    int16_t dummy = n_key_data;
345 	    kadm5_free_key_data (contextp, &dummy, key_data);
346 	}
347 	free (key_data);
348 	krb5_free_principal(contextp->context, princ);
349 	krb5_storage_free(sp);
350 	sp = krb5_storage_emem();
351 	krb5_store_int32(sp, ret);
352 	break;
353     }
354     case kadm_randkey:{
355 	op = "RANDKEY";
356 	ret = krb5_ret_principal(sp, &princ);
357 	if(ret)
358 	    goto fail;
359 	ret = krb5_unparse_name_fixed(contextp->context, princ, name, sizeof(name));
360 	krb5_warnx(contextp->context, "%s: %s %s", client, op, name);
361 	/*
362 	 * The change is allowed if at least one of:
363 	 * a) it's for the principal him/herself and this was an initial ticket
364 	 * b) the user is on the CPW ACL.
365 	 */
366 
367 	if (ret == 0) {
368 	    if (initial
369 		&& krb5_principal_compare (contextp->context, contextp->caller,
370 				       princ))
371 		ret = 0;
372 	    else
373 		ret = _kadm5_acl_check_permission(contextp, KADM5_PRIV_CPW, princ);
374 	}
375 	if(ret) {
376 	    krb5_free_principal(contextp->context, princ);
377 	    goto fail;
378 	}
379 	ret = kadm5_randkey_principal(kadm_handlep, princ,
380 				      &new_keys, &n_keys);
381 	krb5_free_principal(contextp->context, princ);
382 	krb5_storage_free(sp);
383 	sp = krb5_storage_emem();
384 	krb5_store_int32(sp, ret);
385 	if(ret == 0){
386 	    int i;
387 	    krb5_store_int32(sp, n_keys);
388 	    for(i = 0; i < n_keys; i++){
389 		krb5_store_keyblock(sp, new_keys[i]);
390 		krb5_free_keyblock_contents(contextp->context, &new_keys[i]);
391 	    }
392 	    free(new_keys);
393 	}
394 	break;
395     }
396     case kadm_get_privs:{
397 	uint32_t privs;
398 	ret = kadm5_get_privs(kadm_handlep, &privs);
399 	krb5_storage_free(sp);
400 	sp = krb5_storage_emem();
401 	krb5_store_int32(sp, ret);
402 	if(ret == 0)
403 	    krb5_store_uint32(sp, privs);
404 	break;
405     }
406     case kadm_get_princs:{
407 	op = "LIST";
408 	ret = krb5_ret_int32(sp, &tmp);
409 	if(ret)
410 	    goto fail;
411 	if(tmp){
412 	    ret = krb5_ret_string(sp, &expression);
413 	    if(ret)
414 		goto fail;
415 	}else
416 	    expression = NULL;
417 	krb5_warnx(contextp->context, "%s: %s %s", client, op,
418 		   expression ? expression : "*");
419 	ret = _kadm5_acl_check_permission(contextp, KADM5_PRIV_LIST, NULL);
420 	if(ret){
421 	    free(expression);
422 	    goto fail;
423 	}
424 	ret = kadm5_get_principals(kadm_handlep, expression, &princs, &n_princs);
425 	free(expression);
426 	krb5_storage_free(sp);
427 	sp = krb5_storage_emem();
428 	krb5_store_int32(sp, ret);
429 	if(ret == 0){
430 	    int i;
431 	    if ((ret = krb5_store_int32(sp, n_princs)))
432 		goto fail;
433 	    for(i = 0; i < n_princs; i++)
434 		if ((ret = krb5_store_string(sp, princs[i])))
435 			goto fail;
436 	    kadm5_free_name_list(kadm_handlep, princs, &n_princs);
437 	}
438 	break;
439     }
440     default:
441 	krb5_warnx(contextp->context, "%s: UNKNOWN OP %d", client, cmd);
442 	krb5_storage_free(sp);
443 	sp = krb5_storage_emem();
444 	krb5_store_int32(sp, KADM5_FAILURE);
445 	break;
446     }
447     krb5_storage_to_data(sp, out);
448     krb5_storage_free(sp);
449     return 0;
450 fail:
451     krb5_warn(contextp->context, ret, "%s", op);
452     krb5_storage_seek(sp, 0, SEEK_SET);
453     krb5_store_int32(sp, ret);
454     krb5_storage_to_data(sp, out);
455     krb5_storage_free(sp);
456     return ret;
457 }
458 
459 static void
460 v5_loop (krb5_context contextp,
461 	 krb5_auth_context ac,
462 	 krb5_boolean initial,
463 	 void *kadm_handlep,
464 	 krb5_socket_t fd)
465 {
466     krb5_error_code ret;
467     krb5_data in, out;
468 
469     for (;;) {
470 	doing_useful_work = 0;
471 	if(term_flag)
472 	    exit(0);
473 	ret = krb5_read_priv_message(contextp, ac, &fd, &in);
474 	if(ret == HEIM_ERR_EOF)
475 	    exit(0);
476 	if (in.length == 0)
477 	    ret = HEIM_ERR_OPNOTSUPP;
478 	if(ret)
479 	    krb5_err(contextp, 1, ret, "krb5_read_priv_message");
480 	doing_useful_work = 1;
481 	kadmind_dispatch(kadm_handlep, initial, &in, &out);
482 	krb5_data_free(&in);
483 	ret = krb5_write_priv_message(contextp, ac, &fd, &out);
484 	if(ret)
485 	    krb5_err(contextp, 1, ret, "krb5_write_priv_message");
486     }
487 }
488 
489 static krb5_boolean
490 match_appl_version(const void *data, const char *appl_version)
491 {
492     unsigned minor;
493     if(sscanf(appl_version, "KADM0.%u", &minor) != 1)
494 	return 0;
495     /*XXX*/
496     *(unsigned*)(intptr_t)data = minor;
497     return 1;
498 }
499 
500 static void
501 handle_v5(krb5_context contextp,
502 	  krb5_keytab keytab,
503 	  krb5_socket_t fd)
504 {
505     krb5_error_code ret;
506     krb5_ticket *ticket;
507     char *server_name;
508     char *client;
509     void *kadm_handlep;
510     krb5_boolean initial;
511     krb5_auth_context ac = NULL;
512 
513     unsigned kadm_version;
514     kadm5_config_params realm_params;
515 
516     ret = krb5_recvauth_match_version(contextp, &ac, &fd,
517 				      match_appl_version, &kadm_version,
518 				      NULL, KRB5_RECVAUTH_IGNORE_VERSION,
519 				      keytab, &ticket);
520     if (ret)
521 	krb5_err(contextp, 1, ret, "krb5_recvauth");
522 
523     ret = krb5_unparse_name (contextp, ticket->server, &server_name);
524     if (ret)
525 	krb5_err (contextp, 1, ret, "krb5_unparse_name");
526 
527     if (strncmp (server_name, KADM5_ADMIN_SERVICE,
528 		 strlen(KADM5_ADMIN_SERVICE)) != 0)
529 	krb5_errx (contextp, 1, "ticket for strange principal (%s)",
530 		   server_name);
531 
532     free (server_name);
533 
534     memset(&realm_params, 0, sizeof(realm_params));
535 
536     if(kadm_version == 1) {
537 	krb5_data params;
538 	ret = krb5_read_priv_message(contextp, ac, &fd, &params);
539 	if(ret)
540 	    krb5_err(contextp, 1, ret, "krb5_read_priv_message");
541 	ret = _kadm5_unmarshal_params(contextp, &params, &realm_params);
542 	if(ret)
543 	    krb5_err(contextp, 1, ret, "Could not read or parse kadm5 parameters");
544     }
545 
546     initial = ticket->ticket.flags.initial;
547     ret = krb5_unparse_name(contextp, ticket->client, &client);
548     if (ret)
549 	krb5_err (contextp, 1, ret, "krb5_unparse_name");
550     krb5_free_ticket (contextp, ticket);
551     ret = kadm5_s_init_with_password_ctx(contextp,
552 					 client,
553 					 NULL,
554 					 KADM5_ADMIN_SERVICE,
555 					 &realm_params,
556 					 0, 0,
557 					 &kadm_handlep);
558     if(ret)
559 	krb5_err (contextp, 1, ret, "kadm5_init_with_password_ctx");
560     v5_loop (contextp, ac, initial, kadm_handlep, fd);
561 }
562 
563 krb5_error_code
564 kadmind_loop(krb5_context contextp,
565 	     krb5_keytab keytab,
566 	     krb5_socket_t sock)
567 {
568     u_char buf[sizeof(KRB5_SENDAUTH_VERSION) + 4];
569     ssize_t n;
570     unsigned long len;
571 
572     n = krb5_net_read(contextp, &sock, buf, 4);
573     if(n == 0)
574 	exit(0);
575     if(n < 0)
576 	krb5_err(contextp, 1, errno, "read");
577     _krb5_get_int(buf, &len, 4);
578 
579     if (len == sizeof(KRB5_SENDAUTH_VERSION)) {
580 
581 	n = krb5_net_read(contextp, &sock, buf + 4, len);
582 	if (n < 0)
583 	    krb5_err (contextp, 1, errno, "reading sendauth version");
584 	if (n == 0)
585 	    krb5_errx (contextp, 1, "EOF reading sendauth version");
586 
587 	if(memcmp(buf + 4, KRB5_SENDAUTH_VERSION, len) == 0) {
588 	    handle_v5(contextp, keytab, sock);
589 	    return 0;
590 	}
591 	len += 4;
592     } else
593 	len = 4;
594 
595     handle_mit(contextp, buf, len, sock);
596 
597     return 0;
598 }
599