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