xref: /freebsd/crypto/heimdal/kadmin/server.c (revision daf1cffce2e07931f27c6c6998652e90df6ba87e)
1 /*
2  * Copyright (c) 1997 - 2000 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 RCSID("$Id: server.c,v 1.24 2000/01/02 03:58:45 assar Exp $");
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, *exp;
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 
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(context->context, princ);
72 	    goto fail;
73 	}
74 	krb5_unparse_name_fixed(context->context, princ, name, sizeof(name));
75 	krb5_warnx(context->context, "%s: %s %s", client, op, name);
76 	ret = _kadm5_acl_check_permission(context, KADM5_PRIV_GET);
77 	if(ret){
78 	    krb5_free_principal(context->context, princ);
79 	    goto fail;
80 	}
81 	ret = kadm5_get_principal(kadm_handle, princ, &ent, mask);
82 	krb5_storage_free(sp);
83 	sp = krb5_storage_emem();
84 	krb5_store_int32(sp, ret);
85 	if(ret == 0){
86 	    kadm5_store_principal_ent(sp, &ent);
87 	    kadm5_free_principal_ent(kadm_handle, &ent);
88 	}
89 	krb5_free_principal(context->context, princ);
90 	break;
91     }
92     case kadm_delete:{
93 	op = "DELETE";
94 	ret = krb5_ret_principal(sp, &princ);
95 	if(ret)
96 	    goto fail;
97 	krb5_unparse_name_fixed(context->context, princ, name, sizeof(name));
98 	krb5_warnx(context->context, "%s: %s %s", client, op, name);
99 	ret = _kadm5_acl_check_permission(context, KADM5_PRIV_DELETE);
100 	if(ret){
101 	    krb5_free_principal(context->context, princ);
102 	    goto fail;
103 	}
104 	ret = kadm5_delete_principal(kadm_handle, princ);
105 	krb5_free_principal(context->context, princ);
106 	krb5_storage_free(sp);
107 	sp = krb5_storage_emem();
108 	krb5_store_int32(sp, ret);
109 	break;
110     }
111     case kadm_create:{
112 	op = "CREATE";
113 	ret = kadm5_ret_principal_ent(sp, &ent);
114 	if(ret)
115 	    goto fail;
116 	ret = krb5_ret_int32(sp, &mask);
117 	if(ret){
118 	    kadm5_free_principal_ent(context->context, &ent);
119 	    goto fail;
120 	}
121 	ret = krb5_ret_string(sp, &password);
122 	if(ret){
123 	    kadm5_free_principal_ent(context->context, &ent);
124 	    goto fail;
125 	}
126 	krb5_unparse_name_fixed(context->context, ent.principal,
127 				name, sizeof(name));
128 	krb5_warnx(context->context, "%s: %s %s", client, op, name);
129 	ret = _kadm5_acl_check_permission(context, KADM5_PRIV_ADD);
130 	if(ret){
131 	    kadm5_free_principal_ent(context->context, &ent);
132 	    memset(password, 0, strlen(password));
133 	    free(password);
134 	    goto fail;
135 	}
136 	ret = kadm5_create_principal(kadm_handle, &ent,
137 				     mask, password);
138 	kadm5_free_principal_ent(kadm_handle, &ent);
139 	memset(password, 0, strlen(password));
140 	free(password);
141 	krb5_storage_free(sp);
142 	sp = krb5_storage_emem();
143 	krb5_store_int32(sp, ret);
144 	break;
145     }
146     case kadm_modify:{
147 	op = "MODIFY";
148 	ret = kadm5_ret_principal_ent(sp, &ent);
149 	if(ret)
150 	    goto fail;
151 	ret = krb5_ret_int32(sp, &mask);
152 	if(ret){
153 	    kadm5_free_principal_ent(context, &ent);
154 	    goto fail;
155 	}
156 	krb5_unparse_name_fixed(context->context, ent.principal,
157 				name, sizeof(name));
158 	krb5_warnx(context->context, "%s: %s %s", client, op, name);
159 	ret = _kadm5_acl_check_permission(context, KADM5_PRIV_MODIFY);
160 	if(ret){
161 	    kadm5_free_principal_ent(context, &ent);
162 	    goto fail;
163 	}
164 	ret = kadm5_modify_principal(kadm_handle, &ent, mask);
165 	kadm5_free_principal_ent(kadm_handle, &ent);
166 	krb5_storage_free(sp);
167 	sp = krb5_storage_emem();
168 	krb5_store_int32(sp, ret);
169 	break;
170     }
171     case kadm_rename:{
172 	op = "RENAME";
173 	ret = krb5_ret_principal(sp, &princ);
174 	if(ret)
175 	    goto fail;
176 	ret = krb5_ret_principal(sp, &princ2);
177 	if(ret){
178 	    krb5_free_principal(context->context, princ);
179 	    goto fail;
180 	}
181 	krb5_unparse_name_fixed(context->context, princ, name, sizeof(name));
182 	krb5_unparse_name_fixed(context->context, princ2, name2, sizeof(name2));
183 	krb5_warnx(context->context, "%s: %s %s -> %s",
184 		   client, op, name, name2);
185 	ret = _kadm5_acl_check_permission(context,
186 					  KADM5_PRIV_ADD|KADM5_PRIV_DELETE);
187 	if(ret){
188 	    krb5_free_principal(context->context, princ);
189 	    goto fail;
190 	}
191 	ret = kadm5_rename_principal(kadm_handle, princ, princ2);
192 	krb5_free_principal(context->context, princ);
193 	krb5_free_principal(context->context, princ2);
194 	krb5_storage_free(sp);
195 	sp = krb5_storage_emem();
196 	krb5_store_int32(sp, ret);
197 	break;
198     }
199     case kadm_chpass:{
200 	op = "CHPASS";
201 	ret = krb5_ret_principal(sp, &princ);
202 	if(ret)
203 	    goto fail;
204 	ret = krb5_ret_string(sp, &password);
205 	if(ret){
206 	    krb5_free_principal(context->context, princ);
207 	    goto fail;
208 	}
209 	krb5_unparse_name_fixed(context->context, princ, name, sizeof(name));
210 	krb5_warnx(context->context, "%s: %s %s", client, op, name);
211 
212 	/*
213 	 * The change is allowed if at least one of:
214 	 * a) it's for the principal him/herself and this was an initial ticket
215 	 * b) the user is on the CPW ACL.
216 	 */
217 
218 	if (initial
219 	    && krb5_principal_compare (context->context, context->caller,
220 				       princ))
221 	    ret = 0;
222 	else
223 	    ret = _kadm5_acl_check_permission(context, KADM5_PRIV_CPW);
224 
225 	if(ret) {
226 	    krb5_free_principal(context->context, princ);
227 	    goto fail;
228 	}
229 	ret = kadm5_chpass_principal(kadm_handle, princ, password);
230 	krb5_free_principal(context->context, princ);
231 	memset(password, 0, strlen(password));
232 	free(password);
233 	krb5_storage_free(sp);
234 	sp = krb5_storage_emem();
235 	krb5_store_int32(sp, ret);
236 	break;
237     }
238     case kadm_randkey:{
239 	op = "RANDKEY";
240 	ret = krb5_ret_principal(sp, &princ);
241 	if(ret)
242 	    goto fail;
243 	krb5_unparse_name_fixed(context->context, princ, name, sizeof(name));
244 	krb5_warnx(context->context, "%s: %s %s", client, op, name);
245 	/*
246 	 * The change is allowed if at least one of:
247 	 * a) it's for the principal him/herself and this was an initial ticket
248 	 * b) the user is on the CPW ACL.
249 	 */
250 
251 	if (initial
252 	    && krb5_principal_compare (context->context, context->caller,
253 				       princ))
254 	    ret = 0;
255 	else
256 	    ret = _kadm5_acl_check_permission(context, KADM5_PRIV_CPW);
257 
258 	if(ret) {
259 	    krb5_free_principal(context->context, princ);
260 	    goto fail;
261 	}
262 	ret = kadm5_randkey_principal(kadm_handle, princ,
263 				      &new_keys, &n_keys);
264 	krb5_free_principal(context->context, princ);
265 	krb5_storage_free(sp);
266 	sp = krb5_storage_emem();
267 	krb5_store_int32(sp, ret);
268 	if(ret == 0){
269 	    int i;
270 	    krb5_store_int32(sp, n_keys);
271 	    for(i = 0; i < n_keys; i++){
272 		krb5_store_keyblock(sp, new_keys[i]);
273 		krb5_free_keyblock_contents(context->context, &new_keys[i]);
274 	    }
275 	}
276 	break;
277     }
278     case kadm_get_privs:{
279 	ret = kadm5_get_privs(kadm_handle, &mask);
280 	krb5_storage_free(sp);
281 	sp = krb5_storage_emem();
282 	krb5_store_int32(sp, ret);
283 	if(ret == 0)
284 	    krb5_store_int32(sp, mask);
285 	break;
286     }
287     case kadm_get_princs:{
288 	op = "LIST";
289 	ret = krb5_ret_int32(sp, &tmp);
290 	if(ret)
291 	    goto fail;
292 	if(tmp){
293 	    ret = krb5_ret_string(sp, &exp);
294 	    if(ret)
295 		goto fail;
296 	}else
297 	    exp = NULL;
298 	krb5_warnx(context->context, "%s: %s %s", client, op, exp ? exp : "*");
299 	ret = _kadm5_acl_check_permission(context, KADM5_PRIV_LIST);
300 	if(ret){
301 	    free(exp);
302 	    goto fail;
303 	}
304 	ret = kadm5_get_principals(kadm_handle, exp, &princs, &n_princs);
305 	free(exp);
306 	krb5_storage_free(sp);
307 	sp = krb5_storage_emem();
308 	krb5_store_int32(sp, ret);
309 	if(ret == 0){
310 	    int i;
311 	    krb5_store_int32(sp, n_princs);
312 	    for(i = 0; i < n_princs; i++)
313 		krb5_store_string(sp, princs[i]);
314 	    kadm5_free_name_list(kadm_handle, princs, &n_princs);
315 	}
316 	break;
317     }
318     default:
319 	krb5_warnx(context->context, "%s: UNKNOWN OP %d", client, cmd);
320 	krb5_storage_free(sp);
321 	sp = krb5_storage_emem();
322 	krb5_store_int32(sp, KADM5_FAILURE);
323 	break;
324     }
325     krb5_storage_to_data(sp, out);
326     krb5_storage_free(sp);
327     return 0;
328 fail:
329     krb5_warn(context->context, ret, "%s", op);
330     sp->seek(sp, 0, SEEK_SET);
331     krb5_store_int32(sp, ret);
332     krb5_storage_to_data(sp, out);
333     krb5_storage_free(sp);
334     return 0;
335 }
336 
337 static void
338 v5_loop (krb5_context context,
339 	 krb5_auth_context ac,
340 	 krb5_boolean initial,
341 	 void *kadm_handle,
342 	 int fd)
343 {
344     krb5_error_code ret;
345     ssize_t n;
346     unsigned long len;
347     u_char tmp[4];
348     struct iovec iov[2];
349     krb5_data in, out, msg, reply;
350 
351     for (;;) {
352 	n = krb5_net_read(context, &fd, tmp, 4);
353 	if (n < 0)
354 	    krb5_err (context, 1, errno, "krb5_net_read");
355 	if (n == 0)
356 	    exit (0);
357 	_krb5_get_int (tmp, &len, 4);
358 
359 	ret = krb5_data_alloc(&in, len);
360 	if (ret)
361 	    krb5_err (context, 1, ret, "krb5_data_alloc");
362 
363 	n = krb5_net_read(context, &fd, in.data, in.length);
364 	if (n == 0)
365 	    exit (0);
366 	if(n < 0)
367 	    krb5_errx(context, 1, "read error: %d", errno);
368 	ret = krb5_rd_priv(context, ac, &in, &out, NULL);
369 	if (ret)
370 	    krb5_err(context, 1, ret, "krb5_rd_priv");
371 	krb5_data_free(&in);
372 	kadmind_dispatch(kadm_handle, initial, &out, &msg);
373 	krb5_data_free(&out);
374 	ret = krb5_mk_priv(context, ac, &msg, &reply, NULL);
375 	krb5_data_free(&msg);
376 	if(ret)
377 	    krb5_err(context, 1, ret, "krb5_mk_priv");
378 
379 	_krb5_put_int(tmp, reply.length, 4);
380 
381 	iov[0].iov_base = tmp;
382 	iov[0].iov_len  = 4;
383 	iov[1].iov_base = reply.data;
384 	iov[1].iov_len  = reply.length;
385 	n = writev(fd, iov, 2);
386 	krb5_data_free(&reply);
387 	if(n < 0)
388 	    krb5_err(context, 1, errno, "writev");
389 	if(n < iov[0].iov_len + iov[1].iov_len)
390 	    krb5_errx(context, 1, "short write");
391     }
392 }
393 
394 static krb5_boolean
395 match_appl_version(void *data, const char *appl_version)
396 {
397     unsigned minor;
398     if(sscanf(appl_version, "KADM0.%u", &minor) != 1)
399 	return 0;
400     *(unsigned*)data = minor;
401     return 1;
402 }
403 
404 static void
405 handle_v5(krb5_context context,
406 	  krb5_auth_context ac,
407 	  krb5_keytab keytab,
408 	  int len,
409 	  int fd)
410 {
411     krb5_error_code ret;
412     u_char version[sizeof(KRB5_SENDAUTH_VERSION)];
413     krb5_ticket *ticket;
414     krb5_principal server;
415     char *client;
416     void *kadm_handle;
417     ssize_t n;
418     krb5_boolean initial;
419 
420     unsigned kadm_version;
421     kadm5_config_params realm_params;
422 
423     if (len != sizeof(KRB5_SENDAUTH_VERSION))
424 	krb5_errx(context, 1, "bad sendauth len %d", len);
425     n = krb5_net_read(context, &fd, version, len);
426     if (n < 0)
427 	krb5_err (context, 1, errno, "reading sendauth version");
428     if (n == 0)
429 	krb5_errx (context, 1, "EOF reading sendauth version");
430     if(memcmp(version, KRB5_SENDAUTH_VERSION, len) != 0)
431 	krb5_errx(context, 1, "bad sendauth version %.8s", version);
432 
433     ret = krb5_parse_name(context, KADM5_ADMIN_SERVICE, &server);
434     if (ret)
435 	krb5_err (context, 1, ret, "krb5_parse_name %s", KADM5_ADMIN_SERVICE);
436     ret = krb5_recvauth_match_version(context, &ac, &fd,
437 				      match_appl_version, &kadm_version,
438 				      server, KRB5_RECVAUTH_IGNORE_VERSION,
439 				      keytab, &ticket);
440     if(ret == KRB5_KT_NOTFOUND) {
441 	char *name;
442 	krb5_unparse_name(context, server, &name);
443 	krb5_errx(context, 1, "krb5_recvauth: %s (%s)",
444 		  krb5_get_err_text(context, ret),
445 		  name);
446     }
447     krb5_free_principal(context, server);
448 
449     if(ret)
450 	krb5_err(context, 1, ret, "krb5_recvauth");
451 
452     memset(&realm_params, 0, sizeof(realm_params));
453 
454     if(kadm_version == 1) {
455 	krb5_data enc_data, params;
456 	ret = krb5_read_message(context, &fd, &enc_data);
457 	ret = krb5_rd_priv(context, ac, &enc_data, &params, NULL);
458 	krb5_data_free(&enc_data);
459 	_kadm5_unmarshal_params(context, &params, &realm_params);
460     }
461 
462     initial = ticket->ticket.flags.initial;
463     ret = krb5_unparse_name(context, ticket->client, &client);
464     if (ret)
465 	krb5_err (context, 1, ret, "krb5_unparse_name");
466     krb5_free_ticket (context, ticket);
467     ret = kadm5_init_with_password_ctx(context,
468 				       client,
469 				       NULL,
470 				       KADM5_ADMIN_SERVICE,
471 				       &realm_params,
472 				       0, 0,
473 				       &kadm_handle);
474     if(ret)
475 	krb5_err (context, 1, ret, "kadm5_init_with_password_ctx");
476     v5_loop (context, ac, initial, kadm_handle, fd);
477 }
478 
479 krb5_error_code
480 kadmind_loop(krb5_context context,
481 	     krb5_auth_context ac,
482 	     krb5_keytab keytab,
483 	     int fd)
484 {
485     unsigned char tmp[4];
486     ssize_t n;
487     unsigned long len;
488 
489     n = krb5_net_read(context, &fd, tmp, 4);
490     if(n == 0)
491 	exit(0);
492     if(n < 0)
493 	krb5_errx(context, 1, "read error: %d", errno);
494     _krb5_get_int(tmp, &len, 4);
495     if(len > 0xffff && (len & 0xffff) == ('K' << 8) + 'A') {
496 	len >>= 16;
497 #ifdef KRB4
498 	handle_v4(context, len, fd);
499 #else
500 	krb5_errx(context, 1, "packet appears to be version 4");
501 #endif
502     } else {
503 	handle_v5(context, ac, keytab, len, fd);
504     }
505     return 0;
506 }
507