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