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