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