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
37RCSID("$Id: server.c 17611 2006-06-02 22:10:21Z lha $");
38
39static kadm5_ret_t
40kadmind_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
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, princ);
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, princ);
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					  ent.principal);
131	if(ret){
132	    kadm5_free_principal_ent(context->context, &ent);
133	    memset(password, 0, strlen(password));
134	    free(password);
135	    goto fail;
136	}
137	ret = kadm5_create_principal(kadm_handle, &ent,
138				     mask, password);
139	kadm5_free_principal_ent(kadm_handle, &ent);
140	memset(password, 0, strlen(password));
141	free(password);
142	krb5_storage_free(sp);
143	sp = krb5_storage_emem();
144	krb5_store_int32(sp, ret);
145	break;
146    }
147    case kadm_modify:{
148	op = "MODIFY";
149	ret = kadm5_ret_principal_ent(sp, &ent);
150	if(ret)
151	    goto fail;
152	ret = krb5_ret_int32(sp, &mask);
153	if(ret){
154	    kadm5_free_principal_ent(context, &ent);
155	    goto fail;
156	}
157	krb5_unparse_name_fixed(context->context, ent.principal,
158				name, sizeof(name));
159	krb5_warnx(context->context, "%s: %s %s", client, op, name);
160	ret = _kadm5_acl_check_permission(context, KADM5_PRIV_MODIFY,
161					  ent.principal);
162	if(ret){
163	    kadm5_free_principal_ent(context, &ent);
164	    goto fail;
165	}
166	ret = kadm5_modify_principal(kadm_handle, &ent, mask);
167	kadm5_free_principal_ent(kadm_handle, &ent);
168	krb5_storage_free(sp);
169	sp = krb5_storage_emem();
170	krb5_store_int32(sp, ret);
171	break;
172    }
173    case kadm_rename:{
174	op = "RENAME";
175	ret = krb5_ret_principal(sp, &princ);
176	if(ret)
177	    goto fail;
178	ret = krb5_ret_principal(sp, &princ2);
179	if(ret){
180	    krb5_free_principal(context->context, princ);
181	    goto fail;
182	}
183	krb5_unparse_name_fixed(context->context, princ, name, sizeof(name));
184	krb5_unparse_name_fixed(context->context, princ2, name2, sizeof(name2));
185	krb5_warnx(context->context, "%s: %s %s -> %s",
186		   client, op, name, name2);
187	ret = _kadm5_acl_check_permission(context,
188					  KADM5_PRIV_ADD,
189					  princ2)
190	    || _kadm5_acl_check_permission(context,
191					   KADM5_PRIV_DELETE,
192					   princ);
193	if(ret){
194	    krb5_free_principal(context->context, princ);
195	    krb5_free_principal(context->context, princ2);
196	    goto fail;
197	}
198	ret = kadm5_rename_principal(kadm_handle, princ, princ2);
199	krb5_free_principal(context->context, princ);
200	krb5_free_principal(context->context, princ2);
201	krb5_storage_free(sp);
202	sp = krb5_storage_emem();
203	krb5_store_int32(sp, ret);
204	break;
205    }
206    case kadm_chpass:{
207	op = "CHPASS";
208	ret = krb5_ret_principal(sp, &princ);
209	if(ret)
210	    goto fail;
211	ret = krb5_ret_string(sp, &password);
212	if(ret){
213	    krb5_free_principal(context->context, princ);
214	    goto fail;
215	}
216	krb5_unparse_name_fixed(context->context, princ, name, sizeof(name));
217	krb5_warnx(context->context, "%s: %s %s", client, op, name);
218
219	/*
220	 * The change is allowed if at least one of:
221
222	 * a) it's for the principal him/herself and this was an
223	 *    initial ticket, but then, check with the password quality
224	 *    function.
225	 * b) the user is on the CPW ACL.
226	 */
227
228	if (initial
229	    && krb5_principal_compare (context->context, context->caller,
230				       princ))
231	{
232	    krb5_data pwd_data;
233	    const char *pwd_reason;
234
235	    pwd_data.data = password;
236	    pwd_data.length = strlen(password);
237
238	    pwd_reason = kadm5_check_password_quality (context->context,
239						       princ, &pwd_data);
240	    if (pwd_reason != NULL)
241		ret = KADM5_PASS_Q_DICT;
242	    else
243		ret = 0;
244	} else
245	    ret = _kadm5_acl_check_permission(context, KADM5_PRIV_CPW, princ);
246
247	if(ret) {
248	    krb5_free_principal(context->context, princ);
249	    memset(password, 0, strlen(password));
250	    free(password);
251	    goto fail;
252	}
253	ret = kadm5_chpass_principal(kadm_handle, princ, password);
254	krb5_free_principal(context->context, princ);
255	memset(password, 0, strlen(password));
256	free(password);
257	krb5_storage_free(sp);
258	sp = krb5_storage_emem();
259	krb5_store_int32(sp, ret);
260	break;
261    }
262    case kadm_chpass_with_key:{
263	int i;
264	krb5_key_data *key_data;
265	int n_key_data;
266
267	op = "CHPASS_WITH_KEY";
268	ret = krb5_ret_principal(sp, &princ);
269	if(ret)
270	    goto fail;
271	ret = krb5_ret_int32(sp, &n_key_data);
272	if (ret) {
273	    krb5_free_principal(context->context, princ);
274	    goto fail;
275	}
276	/* n_key_data will be squeezed into an int16_t below. */
277	if (n_key_data < 0 || n_key_data >= 1 << 16 ||
278	    n_key_data > UINT_MAX/sizeof(*key_data)) {
279	    ret = ERANGE;
280	    krb5_free_principal(context->context, princ);
281	    goto fail;
282	}
283
284	key_data = malloc (n_key_data * sizeof(*key_data));
285	if (key_data == NULL) {
286	    ret = ENOMEM;
287	    krb5_free_principal(context->context, princ);
288	    goto fail;
289	}
290
291	for (i = 0; i < n_key_data; ++i) {
292	    ret = kadm5_ret_key_data (sp, &key_data[i]);
293	    if (ret) {
294		int16_t dummy = i;
295
296		kadm5_free_key_data (context, &dummy, key_data);
297		free (key_data);
298		krb5_free_principal(context->context, princ);
299		goto fail;
300	    }
301	}
302
303	krb5_unparse_name_fixed(context->context, princ, name, sizeof(name));
304	krb5_warnx(context->context, "%s: %s %s", client, op, name);
305
306	/*
307	 * The change is only allowed if the user is on the CPW ACL,
308	 * this it to force password quality check on the user.
309	 */
310
311	ret = _kadm5_acl_check_permission(context, KADM5_PRIV_CPW, princ);
312	if(ret) {
313	    int16_t dummy = n_key_data;
314
315	    kadm5_free_key_data (context, &dummy, key_data);
316	    free (key_data);
317	    krb5_free_principal(context->context, princ);
318	    goto fail;
319	}
320	ret = kadm5_chpass_principal_with_key(kadm_handle, princ,
321					      n_key_data, key_data);
322	{
323	    int16_t dummy = n_key_data;
324	    kadm5_free_key_data (context, &dummy, key_data);
325	}
326	free (key_data);
327	krb5_free_principal(context->context, princ);
328	krb5_storage_free(sp);
329	sp = krb5_storage_emem();
330	krb5_store_int32(sp, ret);
331	break;
332    }
333    case kadm_randkey:{
334	op = "RANDKEY";
335	ret = krb5_ret_principal(sp, &princ);
336	if(ret)
337	    goto fail;
338	krb5_unparse_name_fixed(context->context, princ, name, sizeof(name));
339	krb5_warnx(context->context, "%s: %s %s", client, op, name);
340	/*
341	 * The change is allowed if at least one of:
342	 * a) it's for the principal him/herself and this was an initial ticket
343	 * b) the user is on the CPW ACL.
344	 */
345
346	if (initial
347	    && krb5_principal_compare (context->context, context->caller,
348				       princ))
349	    ret = 0;
350	else
351	    ret = _kadm5_acl_check_permission(context, KADM5_PRIV_CPW, princ);
352
353	if(ret) {
354	    krb5_free_principal(context->context, princ);
355	    goto fail;
356	}
357	ret = kadm5_randkey_principal(kadm_handle, princ,
358				      &new_keys, &n_keys);
359	krb5_free_principal(context->context, princ);
360	krb5_storage_free(sp);
361	sp = krb5_storage_emem();
362	krb5_store_int32(sp, ret);
363	if(ret == 0){
364	    int i;
365	    krb5_store_int32(sp, n_keys);
366	    for(i = 0; i < n_keys; i++){
367		krb5_store_keyblock(sp, new_keys[i]);
368		krb5_free_keyblock_contents(context->context, &new_keys[i]);
369	    }
370	}
371	break;
372    }
373    case kadm_get_privs:{
374	uint32_t privs;
375	ret = kadm5_get_privs(kadm_handle, &privs);
376	krb5_storage_free(sp);
377	sp = krb5_storage_emem();
378	krb5_store_int32(sp, ret);
379	if(ret == 0)
380	    krb5_store_uint32(sp, privs);
381	break;
382    }
383    case kadm_get_princs:{
384	op = "LIST";
385	ret = krb5_ret_int32(sp, &tmp);
386	if(ret)
387	    goto fail;
388	if(tmp){
389	    ret = krb5_ret_string(sp, &expression);
390	    if(ret)
391		goto fail;
392	}else
393	    expression = NULL;
394	krb5_warnx(context->context, "%s: %s %s", client, op,
395		   expression ? expression : "*");
396	ret = _kadm5_acl_check_permission(context, KADM5_PRIV_LIST, NULL);
397	if(ret){
398	    free(expression);
399	    goto fail;
400	}
401	ret = kadm5_get_principals(kadm_handle, expression, &princs, &n_princs);
402	free(expression);
403	krb5_storage_free(sp);
404	sp = krb5_storage_emem();
405	krb5_store_int32(sp, ret);
406	if(ret == 0){
407	    int i;
408	    krb5_store_int32(sp, n_princs);
409	    for(i = 0; i < n_princs; i++)
410		krb5_store_string(sp, princs[i]);
411	    kadm5_free_name_list(kadm_handle, princs, &n_princs);
412	}
413	break;
414    }
415    default:
416	krb5_warnx(context->context, "%s: UNKNOWN OP %d", client, cmd);
417	krb5_storage_free(sp);
418	sp = krb5_storage_emem();
419	krb5_store_int32(sp, KADM5_FAILURE);
420	break;
421    }
422    krb5_storage_to_data(sp, out);
423    krb5_storage_free(sp);
424    return 0;
425fail:
426    krb5_warn(context->context, ret, "%s", op);
427    krb5_storage_seek(sp, 0, SEEK_SET);
428    krb5_store_int32(sp, ret);
429    krb5_storage_to_data(sp, out);
430    krb5_storage_free(sp);
431    return 0;
432}
433
434static void
435v5_loop (krb5_context context,
436	 krb5_auth_context ac,
437	 krb5_boolean initial,
438	 void *kadm_handle,
439	 int fd)
440{
441    krb5_error_code ret;
442    krb5_data in, out;
443
444    for (;;) {
445	doing_useful_work = 0;
446	if(term_flag)
447	    exit(0);
448	ret = krb5_read_priv_message(context, ac, &fd, &in);
449	if(ret == HEIM_ERR_EOF)
450	    exit(0);
451	if(ret)
452	    krb5_err(context, 1, ret, "krb5_read_priv_message");
453	doing_useful_work = 1;
454	kadmind_dispatch(kadm_handle, initial, &in, &out);
455	krb5_data_free(&in);
456	ret = krb5_write_priv_message(context, ac, &fd, &out);
457	if(ret)
458	    krb5_err(context, 1, ret, "krb5_write_priv_message");
459    }
460}
461
462static krb5_boolean
463match_appl_version(const void *data, const char *appl_version)
464{
465    unsigned minor;
466    if(sscanf(appl_version, "KADM0.%u", &minor) != 1)
467	return 0;
468    *(unsigned*)data = minor;
469    return 1;
470}
471
472static void
473handle_v5(krb5_context context,
474	  krb5_auth_context ac,
475	  krb5_keytab keytab,
476	  int len,
477	  int fd)
478{
479    krb5_error_code ret;
480    u_char version[sizeof(KRB5_SENDAUTH_VERSION)];
481    krb5_ticket *ticket;
482    char *server_name;
483    char *client;
484    void *kadm_handle;
485    ssize_t n;
486    krb5_boolean initial;
487
488    unsigned kadm_version;
489    kadm5_config_params realm_params;
490
491    if (len != sizeof(KRB5_SENDAUTH_VERSION))
492	krb5_errx(context, 1, "bad sendauth len %d", len);
493    n = krb5_net_read(context, &fd, version, len);
494    if (n < 0)
495	krb5_err (context, 1, errno, "reading sendauth version");
496    if (n == 0)
497	krb5_errx (context, 1, "EOF reading sendauth version");
498    if(memcmp(version, KRB5_SENDAUTH_VERSION, len) != 0)
499	krb5_errx(context, 1, "bad sendauth version %.8s", version);
500
501    ret = krb5_recvauth_match_version(context, &ac, &fd,
502				      match_appl_version, &kadm_version,
503				      NULL, KRB5_RECVAUTH_IGNORE_VERSION,
504				      keytab, &ticket);
505    if(ret == KRB5_KT_NOTFOUND)
506	krb5_errx(context, 1, "krb5_recvauth: key not found");
507    if(ret)
508	krb5_err(context, 1, ret, "krb5_recvauth");
509
510    ret = krb5_unparse_name (context, ticket->server, &server_name);
511    if (ret)
512	krb5_err (context, 1, ret, "krb5_unparse_name");
513
514    if (strncmp (server_name, KADM5_ADMIN_SERVICE,
515		 strlen(KADM5_ADMIN_SERVICE)) != 0)
516	krb5_errx (context, 1, "ticket for strange principal (%s)",
517		   server_name);
518
519    free (server_name);
520
521    memset(&realm_params, 0, sizeof(realm_params));
522
523    if(kadm_version == 1) {
524	krb5_data params;
525	ret = krb5_read_priv_message(context, ac, &fd, &params);
526	if(ret)
527	    krb5_err(context, 1, ret, "krb5_read_priv_message");
528	_kadm5_unmarshal_params(context, &params, &realm_params);
529    }
530
531    initial = ticket->ticket.flags.initial;
532    ret = krb5_unparse_name(context, ticket->client, &client);
533    if (ret)
534	krb5_err (context, 1, ret, "krb5_unparse_name");
535    krb5_free_ticket (context, ticket);
536    ret = kadm5_init_with_password_ctx(context,
537				       client,
538				       NULL,
539				       KADM5_ADMIN_SERVICE,
540				       &realm_params,
541				       0, 0,
542				       &kadm_handle);
543    if(ret)
544	krb5_err (context, 1, ret, "kadm5_init_with_password_ctx");
545    v5_loop (context, ac, initial, kadm_handle, fd);
546}
547
548krb5_error_code
549kadmind_loop(krb5_context context,
550	     krb5_auth_context ac,
551	     krb5_keytab keytab,
552	     int fd)
553{
554    unsigned char tmp[4];
555    ssize_t n;
556    unsigned long len;
557
558    n = krb5_net_read(context, &fd, tmp, 4);
559    if(n == 0)
560	exit(0);
561    if(n < 0)
562	krb5_err(context, 1, errno, "read");
563    _krb5_get_int(tmp, &len, 4);
564    /* this v4 test could probably also go away */
565    if(len > 0xffff && (len & 0xffff) == ('K' << 8) + 'A') {
566	unsigned char v4reply[] = {
567	    0x00, 0x0c,
568	    'K', 'Y', 'O', 'U', 'L', 'O', 'S', 'E',
569	    0x95, 0xb7, 0xa7, 0x08 /* KADM_BAD_VER */
570	};
571	krb5_net_write(context, &fd, v4reply, sizeof(v4reply));
572	krb5_errx(context, 1, "packet appears to be version 4");
573    } else {
574	handle_v5(context, ac, keytab, len, fd);
575    }
576    return 0;
577}
578