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