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