initial ?
[metze/heimdal/wip.git] / kadmin / server.c
1 /*
2  * Copyright (c) 1997-1999 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. All advertising materials mentioning features or use of this software 
18  *    must display the following acknowledgement: 
19  *      This product includes software developed by Kungliga Tekniska 
20  *      Högskolan and its contributors. 
21  *
22  * 4. Neither the name of the Institute nor the names of its contributors 
23  *    may be used to endorse or promote products derived from this software 
24  *    without specific prior written permission. 
25  *
26  * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND 
27  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
28  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
29  * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE 
30  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
31  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 
32  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
33  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
34  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 
35  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 
36  * SUCH DAMAGE. 
37  */
38
39 #include "kadmin_locl.h"
40 #include <krb5-private.h>
41
42 RCSID("$Id$");
43
44 static kadm5_ret_t
45 kadmind_dispatch(void *kadm_handle, krb5_data *in, krb5_data *out)
46 {
47     kadm5_ret_t ret;
48     int32_t cmd, mask, tmp;
49     kadm5_server_context *context = kadm_handle;
50     char client[128], name[128], name2[128];
51     char *op = "";
52     krb5_principal princ, princ2;
53     kadm5_principal_ent_rec ent;
54     char *password, *exp;
55     krb5_keyblock *new_keys;
56     int n_keys;
57     char **princs;
58     int n_princs;
59     krb5_storage *sp;
60     
61     krb5_unparse_name_fixed(context->context, context->caller, 
62                             client, sizeof(client));
63     
64     sp = krb5_storage_from_data(in);
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(context->context, princ);
76             goto fail;
77         }
78         krb5_unparse_name_fixed(context->context, princ, name, sizeof(name));
79         krb5_warnx(context->context, "%s: %s %s", client, op, name);
80         ret = _kadm5_acl_check_permission(context, KADM5_PRIV_GET);
81         if(ret){
82             krb5_free_principal(context->context, princ);
83             goto fail;
84         }
85         ret = kadm5_get_principal(kadm_handle, princ, &ent, mask);
86         krb5_storage_free(sp);
87         sp = krb5_storage_emem();
88         krb5_store_int32(sp, ret);
89         if(ret == 0){
90             kadm5_store_principal_ent(sp, &ent);
91             kadm5_free_principal_ent(kadm_handle, &ent);
92         }
93         krb5_free_principal(context->context, princ);
94         break;
95     }
96     case kadm_delete:{
97         op = "DELETE";
98         ret = krb5_ret_principal(sp, &princ);
99         if(ret)
100             goto fail;
101         krb5_unparse_name_fixed(context->context, princ, name, sizeof(name));
102         krb5_warnx(context->context, "%s: %s %s", client, op, name);
103         ret = _kadm5_acl_check_permission(context, KADM5_PRIV_DELETE);
104         if(ret){
105             krb5_free_principal(context->context, princ);
106             goto fail;
107         }
108         ret = kadm5_delete_principal(kadm_handle, princ);
109         krb5_free_principal(context->context, princ);
110         krb5_storage_free(sp);
111         sp = krb5_storage_emem();
112         krb5_store_int32(sp, ret);
113         break;
114     }
115     case kadm_create:{
116         op = "CREATE";
117         ret = kadm5_ret_principal_ent(sp, &ent);
118         if(ret)
119             goto fail;
120         ret = krb5_ret_int32(sp, &mask);
121         if(ret){
122             kadm5_free_principal_ent(context->context, &ent);
123             goto fail;
124         }
125         ret = krb5_ret_string(sp, &password);
126         if(ret){
127             kadm5_free_principal_ent(context->context, &ent);
128             goto fail;
129         }
130         krb5_unparse_name_fixed(context->context, ent.principal, 
131                                 name, sizeof(name));
132         krb5_warnx(context->context, "%s: %s %s", client, op, name);
133         ret = _kadm5_acl_check_permission(context, KADM5_PRIV_ADD);
134         if(ret){
135             kadm5_free_principal_ent(context->context, &ent);
136             memset(password, 0, strlen(password));
137             free(password);
138             goto fail;
139         }
140         ret = kadm5_create_principal(kadm_handle, &ent, 
141                                      mask, password);
142         kadm5_free_principal_ent(kadm_handle, &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(context, &ent);
158             goto fail;
159         }
160         krb5_unparse_name_fixed(context->context, ent.principal, 
161                                 name, sizeof(name));
162         krb5_warnx(context->context, "%s: %s %s", client, op, name);
163         ret = _kadm5_acl_check_permission(context, KADM5_PRIV_MODIFY);
164         if(ret){
165             kadm5_free_principal_ent(context, &ent);
166             goto fail;
167         }
168         ret = kadm5_modify_principal(kadm_handle, &ent, mask);
169         kadm5_free_principal_ent(kadm_handle, &ent);
170         krb5_storage_free(sp);
171         sp = krb5_storage_emem();
172         krb5_store_int32(sp, ret);
173         break;
174     }
175     case kadm_rename:{
176         op = "RENAME";
177         ret = krb5_ret_principal(sp, &princ);
178         if(ret)
179             goto fail;
180         ret = krb5_ret_principal(sp, &princ2);
181         if(ret){
182             krb5_free_principal(context->context, princ);
183             goto fail;
184         }
185         krb5_unparse_name_fixed(context->context, princ, name, sizeof(name));
186         krb5_unparse_name_fixed(context->context, princ2, name2, sizeof(name2));
187         krb5_warnx(context->context, "%s: %s %s -> %s", 
188                    client, op, name, name2);
189         ret = _kadm5_acl_check_permission(context, 
190                                           KADM5_PRIV_ADD|KADM5_PRIV_DELETE);
191         if(ret){
192             krb5_free_principal(context->context, princ);
193             goto fail;
194         }
195         ret = kadm5_rename_principal(kadm_handle, princ, princ2);
196         krb5_free_principal(context->context, princ);
197         krb5_free_principal(context->context, princ2);
198         krb5_storage_free(sp);
199         sp = krb5_storage_emem();
200         krb5_store_int32(sp, ret);
201         break;
202     }
203     case kadm_chpass:{
204         op = "CHPASS";
205         ret = krb5_ret_principal(sp, &princ);
206         if(ret)
207             goto fail;
208         ret = krb5_ret_string(sp, &password);
209         if(ret){
210             krb5_free_principal(context->context, princ);
211             goto fail;
212         }
213         krb5_unparse_name_fixed(context->context, princ, name, sizeof(name));
214         krb5_warnx(context->context, "%s: %s %s", client, op, name);
215 #if 0
216         /* anyone can change her/his own password */
217         /* but not until there is a way to ensure that the
218            authentication was done via an initial ticket request */
219         if(!krb5_principal_compare(context->context, context->caller, princ))
220             ret = KADM5_AUTH_INSUFFICIENT;
221         if(ret)
222 #endif
223             ret = _kadm5_acl_check_permission(context, KADM5_PRIV_CPW);
224         if(ret){
225             krb5_free_principal(context->context, princ);
226             goto fail;
227         }
228         ret = kadm5_chpass_principal(kadm_handle, princ, password);
229         krb5_free_principal(context->context, princ);
230         memset(password, 0, strlen(password));
231         free(password);
232         krb5_storage_free(sp);
233         sp = krb5_storage_emem();
234         krb5_store_int32(sp, ret);
235         break;
236     }
237     case kadm_randkey:{
238         op = "RANDKEY";
239         ret = krb5_ret_principal(sp, &princ);
240         if(ret)
241             goto fail;
242         krb5_unparse_name_fixed(context->context, princ, name, sizeof(name));
243         krb5_warnx(context->context, "%s: %s %s", client, op, name);
244 #if 0
245         /* anyone can change her/his own password */
246         /* but not until there is a way to ensure that the
247            authentication was done via an initial ticket request */
248         if(!krb5_principal_compare(context->context, context->caller, princ))
249             ret = KADM5_AUTH_INSUFFICIENT;
250         if(ret)
251 #endif
252             ret = _kadm5_acl_check_permission(context, KADM5_PRIV_CPW);
253         if(ret){
254             krb5_free_principal(context->context, princ);
255             goto fail;
256         }
257         ret = kadm5_randkey_principal(kadm_handle, princ, 
258                                       &new_keys, &n_keys);
259         krb5_free_principal(context->context, princ);
260         krb5_storage_free(sp);
261         sp = krb5_storage_emem();
262         krb5_store_int32(sp, ret);
263         if(ret == 0){
264             int i;
265             krb5_store_int32(sp, n_keys);
266             for(i = 0; i < n_keys; i++){
267                 krb5_store_keyblock(sp, new_keys[i]);
268                 krb5_free_keyblock_contents(context->context, &new_keys[i]);
269             }
270         }
271         break;
272     }
273     case kadm_get_privs:{
274         ret = kadm5_get_privs(kadm_handle, &mask);
275         krb5_storage_free(sp);
276         sp = krb5_storage_emem();
277         krb5_store_int32(sp, ret);
278         if(ret == 0)
279             krb5_store_int32(sp, mask);
280         break;
281     }
282     case kadm_get_princs:{
283         op = "LIST";
284         ret = krb5_ret_int32(sp, &tmp);
285         if(ret)
286             goto fail;
287         if(tmp){
288             ret = krb5_ret_string(sp, &exp);
289             if(ret)
290                 goto fail;
291         }else
292             exp = NULL;
293         krb5_warnx(context->context, "%s: %s %s", client, op, exp ? exp : "*");
294         ret = _kadm5_acl_check_permission(context, KADM5_PRIV_LIST);
295         if(ret){
296             free(exp);
297             goto fail;
298         }
299         ret = kadm5_get_principals(kadm_handle, exp, &princs, &n_princs);
300         free(exp);
301         krb5_storage_free(sp);
302         sp = krb5_storage_emem();
303         krb5_store_int32(sp, ret);
304         if(ret == 0){
305             int i;
306             krb5_store_int32(sp, n_princs);
307             for(i = 0; i < n_princs; i++)
308                 krb5_store_string(sp, princs[i]);
309             kadm5_free_name_list(kadm_handle, princs, &n_princs);
310         }
311         break;
312     }
313     default:
314         krb5_warnx(context->context, "%s: UNKNOWN OP %d", client, cmd);
315         krb5_storage_free(sp);
316         sp = krb5_storage_emem();
317         krb5_store_int32(sp, KADM5_FAILURE);
318         break;
319     }
320     krb5_storage_to_data(sp, out);
321     krb5_storage_free(sp);
322     return 0;
323 fail:
324     krb5_warn(context->context, ret, "%s", op);
325     sp->seek(sp, 0, SEEK_SET);
326     krb5_store_int32(sp, ret);
327     krb5_storage_to_data(sp, out);
328     krb5_storage_free(sp);
329     return 0;
330 }
331
332 static void
333 v5_loop (krb5_context context,
334          krb5_auth_context ac,
335          void *kadm_handle,
336          int fd)
337 {
338     krb5_error_code ret;
339     ssize_t n;
340     unsigned long len;
341     u_char tmp[4];
342     struct iovec iov[2];
343     krb5_data in, out, msg, reply;
344
345     for (;;) {
346         n = krb5_net_read(context, &fd, tmp, 4);
347         if (n < 0)
348             krb5_err (context, 1, errno, "krb5_net_read");
349         if (n == 0)
350             exit (0);
351         _krb5_get_int (tmp, &len, 4);
352
353         ret = krb5_data_alloc(&in, len);
354         if (ret)
355             krb5_err (context, 1, ret, "krb5_data_alloc");
356
357         n = krb5_net_read(context, &fd, in.data, in.length);
358         if (n == 0)
359             exit (0);
360         if(n < 0)
361             krb5_errx(context, 1, "read error: %d", errno);
362         ret = krb5_rd_priv(context, ac, &in, &out, NULL);
363         if (ret)
364             krb5_err(context, 1, ret, "krb5_rd_priv");
365         krb5_data_free(&in);
366         kadmind_dispatch(kadm_handle, &out, &msg);
367         krb5_data_free(&out);
368         ret = krb5_mk_priv(context, ac, &msg, &reply, NULL);
369         krb5_data_free(&msg);
370         if(ret) 
371             krb5_err(context, 1, ret, "krb5_mk_priv");
372
373         _krb5_put_int(tmp, reply.length, 4);
374
375         iov[0].iov_base = tmp;
376         iov[0].iov_len  = 4;
377         iov[1].iov_base = reply.data;
378         iov[1].iov_len  = reply.length;
379         n = writev(fd, iov, 2);
380         krb5_data_free(&reply);
381         if(n < 0)
382             krb5_err(context, 1, errno, "writev");
383         if(n < iov[0].iov_len + iov[1].iov_len)
384             krb5_errx(context, 1, "short write");
385     }
386 }
387
388 static krb5_boolean
389 match_appl_version(void *data, const char *appl_version)
390 {
391     unsigned minor;
392     if(sscanf(appl_version, "KADM0.%u", &minor) != 1)
393         return 0;
394     *(unsigned*)data = minor;
395     return 1;
396 }
397
398 static void
399 handle_v5(krb5_context context,
400           krb5_auth_context ac,
401           krb5_keytab keytab,
402           int len,
403           int fd)
404 {
405     krb5_error_code ret;
406     u_char version[sizeof(KRB5_SENDAUTH_VERSION)];
407     krb5_ticket *ticket;
408     krb5_principal server;
409     char *client;
410     void *kadm_handle;
411     ssize_t n;
412
413     unsigned kadm_version;
414     kadm5_config_params realm_params;
415
416     if (len != sizeof(KRB5_SENDAUTH_VERSION))
417         krb5_errx(context, 1, "bad sendauth len %d", len);
418     n = krb5_net_read(context, &fd, version, len);
419     if (n < 0)
420         krb5_err (context, 1, errno, "reading sendauth version");
421     if (n == 0)
422         krb5_errx (context, 1, "EOF reading sendauth version");
423     if(memcmp(version, KRB5_SENDAUTH_VERSION, len) != 0)
424         krb5_errx(context, 1, "bad sendauth version %.8s", version);
425         
426     ret = krb5_parse_name(context, KADM5_ADMIN_SERVICE, &server);
427     if (ret)
428         krb5_err (context, 1, ret, "krb5_parse_name %s", KADM5_ADMIN_SERVICE);
429     ret = krb5_recvauth_match_version(context, &ac, &fd, 
430                                       match_appl_version, &kadm_version,
431                                       server, KRB5_RECVAUTH_IGNORE_VERSION, 
432                                       keytab, &ticket);
433     if(ret == KRB5_KT_NOTFOUND) {
434         char *name;
435         krb5_unparse_name(context, server, &name);
436         krb5_errx(context, 1, "krb5_recvauth: %s (%s)", 
437                   krb5_get_err_text(context, ret),
438                   name);
439     }
440     krb5_free_principal(context, server);
441             
442     if(ret)
443         krb5_err(context, 1, ret, "krb5_recvauth");
444
445     memset(&realm_params, 0, sizeof(realm_params));
446
447     if(kadm_version == 1) {
448         krb5_data enc_data, params;
449         ret = krb5_read_message(context, &fd, &enc_data);
450         ret = krb5_rd_priv(context, ac, &enc_data, &params, NULL);
451         krb5_data_free(&enc_data);
452         _kadm5_unmarshal_params(context, &params, &realm_params);
453     }
454
455     ticket->ticket.flags.initial; /* XXX ? */
456     ret = krb5_unparse_name(context, ticket->client, &client);
457     if (ret)
458         krb5_err (context, 1, ret, "krb5_unparse_name");
459     ret = kadm5_init_with_password_ctx(context, 
460                                        client, 
461                                        NULL,
462                                        KADM5_ADMIN_SERVICE,
463                                        &realm_params, 
464                                        0, 0, 
465                                        &kadm_handle);
466     if(ret)
467         krb5_err (context, 1, ret, "kadm5_init_with_password_ctx");
468     v5_loop (context, ac, kadm_handle, fd);
469 }
470
471 krb5_error_code
472 kadmind_loop(krb5_context context,
473              krb5_auth_context ac,
474              krb5_keytab keytab, 
475              int fd)
476 {
477     unsigned char tmp[4];
478     ssize_t n;
479     unsigned long len;
480
481     n = krb5_net_read(context, &fd, tmp, 4);
482     if(n == 0)
483         exit(0);
484     if(n < 0)
485         krb5_errx(context, 1, "read error: %d", errno);
486     _krb5_get_int(tmp, &len, 4);
487     if(len > 0xffff && (len & 0xffff) == ('K' << 8) + 'A') {
488         len >>= 16;
489 #ifdef KRB4
490         handle_v4(context, len, fd);
491 #else
492         krb5_errx(context, 1, "packet appears to be version 4");
493 #endif
494     } else {
495         handle_v5(context, ac, keytab, len, fd);
496     }
497     return 0;
498 }