remove gcc34
[dragonfly.git] / crypto / heimdal-0.6.3 / lib / kadm5 / ipropd_master.c
1 /*
2  * Copyright (c) 1997 - 2002 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 "iprop.h"
35 #include <rtbl.h>
36
37 RCSID("$Id: ipropd_master.c,v 1.29 2003/03/19 11:56:38 lha Exp $");
38
39 static krb5_log_facility *log_facility;
40
41 const char *slave_stats_file = KADM5_SLAVE_STATS;
42
43 static int
44 make_signal_socket (krb5_context context)
45 {
46     struct sockaddr_un addr;
47     int fd;
48
49     fd = socket (AF_UNIX, SOCK_DGRAM, 0);
50     if (fd < 0)
51         krb5_err (context, 1, errno, "socket AF_UNIX");
52     memset (&addr, 0, sizeof(addr));
53     addr.sun_family = AF_UNIX;
54     strlcpy (addr.sun_path, KADM5_LOG_SIGNAL, sizeof(addr.sun_path));
55     unlink (addr.sun_path);
56     if (bind (fd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
57         krb5_err (context, 1, errno, "bind %s", addr.sun_path);
58     return fd;
59 }
60
61 static int
62 make_listen_socket (krb5_context context)
63 {
64     int fd;
65     int one = 1;
66     struct sockaddr_in addr;
67
68     fd = socket (AF_INET, SOCK_STREAM, 0);
69     if (fd < 0)
70         krb5_err (context, 1, errno, "socket AF_INET");
71     setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, (void *)&one, sizeof(one));
72     memset (&addr, 0, sizeof(addr));
73     addr.sin_family = AF_INET;
74     addr.sin_port   = krb5_getportbyname (context,
75                                           IPROP_SERVICE, "tcp", IPROP_PORT);
76     if(bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
77         krb5_err (context, 1, errno, "bind");
78     if (listen(fd, SOMAXCONN) < 0)
79         krb5_err (context, 1, errno, "listen");
80     return fd;
81 }
82
83 struct slave {
84     int fd;
85     struct sockaddr_in addr;
86     char *name;
87     krb5_auth_context ac;
88     u_int32_t version;
89     time_t seen;
90     unsigned long flags;
91 #define SLAVE_F_DEAD    0x1
92     struct slave *next;
93 };
94
95 typedef struct slave slave;
96
97 static int
98 check_acl (krb5_context context, const char *name)
99 {
100     FILE *fp;
101     char buf[256];
102     int ret = 1;
103
104     fp = fopen (KADM5_SLAVE_ACL, "r");
105     if (fp == NULL)
106         return 1;
107     while (fgets(buf, sizeof(buf), fp) != NULL) {
108         if (buf[strlen(buf) - 1 ] == '\n')
109             buf[strlen(buf) - 1 ] = '\0';
110         if (strcmp (buf, name) == 0) {
111             ret = 0;
112             break;
113         }
114     }
115     fclose (fp);
116     return ret;
117 }
118
119 static void
120 slave_seen(slave *s)
121 {
122     s->seen = time(NULL);
123 }
124
125 static void
126 slave_dead(slave *s)
127 {
128     if (s->fd >= 0) {
129         close (s->fd);
130         s->fd = -1;
131     }
132     s->flags |= SLAVE_F_DEAD;
133     slave_seen(s);
134 }
135
136 static void
137 remove_slave (krb5_context context, slave *s, slave **root)
138 {
139     slave **p;
140
141     if (s->fd >= 0)
142         close (s->fd);
143     if (s->name)
144         free (s->name);
145     if (s->ac)
146         krb5_auth_con_free (context, s->ac);
147
148     for (p = root; *p; p = &(*p)->next)
149         if (*p == s) {
150             *p = s->next;
151             break;
152         }
153     free (s);
154 }
155
156 static void
157 add_slave (krb5_context context, krb5_keytab keytab, slave **root, int fd)
158 {
159     krb5_principal server;
160     krb5_error_code ret;
161     slave *s;
162     socklen_t addr_len;
163     krb5_ticket *ticket = NULL;
164     char hostname[128];
165
166     s = malloc(sizeof(*s));
167     if (s == NULL) {
168         krb5_warnx (context, "add_slave: no memory");
169         return;
170     }
171     s->name = NULL;
172     s->ac = NULL;
173
174     addr_len = sizeof(s->addr);
175     s->fd = accept (fd, (struct sockaddr *)&s->addr, &addr_len);
176     if (s->fd < 0) {
177         krb5_warn (context, errno, "accept");
178         goto error;
179     }
180     gethostname(hostname, sizeof(hostname));
181     ret = krb5_sname_to_principal (context, hostname, IPROP_NAME,
182                                    KRB5_NT_SRV_HST, &server);
183     if (ret) {
184         krb5_warn (context, ret, "krb5_sname_to_principal");
185         goto error;
186     }
187
188     ret = krb5_recvauth (context, &s->ac, &s->fd,
189                          IPROP_VERSION, server, 0, keytab, &ticket);
190     krb5_free_principal (context, server);
191     if (ret) {
192         krb5_warn (context, ret, "krb5_recvauth");
193         goto error;
194     }
195     ret = krb5_unparse_name (context, ticket->client, &s->name);
196     if (ret) {
197         krb5_warn (context, ret, "krb5_unparse_name");
198         goto error;
199     }
200     if (check_acl (context, s->name)) {
201         krb5_warnx (context, "%s not in acl", s->name);
202         goto error;
203     }
204     krb5_free_ticket (context, ticket);
205     ticket = NULL;
206
207     {
208         slave *l = *root;
209
210         while (l) {
211             if (strcmp(l->name, s->name) == 0)
212                 break;
213             l = l->next;
214         }
215         if (l) {
216             if (l->flags & SLAVE_F_DEAD) {
217                 remove_slave(context, l, root);
218             } else {
219                 krb5_warnx (context, "second connection from %s", s->name);
220                 goto error;
221             }
222         }
223     }
224
225     krb5_warnx (context, "connection from %s", s->name);
226
227     s->version = 0;
228     s->flags = 0;
229     slave_seen(s);
230     s->next = *root;
231     *root = s;
232     return;
233 error:
234     remove_slave(context, s, root);
235 }
236
237 struct prop_context {
238     krb5_auth_context auth_context;
239     int fd;
240 };
241
242 static int
243 prop_one (krb5_context context, HDB *db, hdb_entry *entry, void *v)
244 {
245     krb5_error_code ret;
246     krb5_data data;
247     struct slave *slave = (struct slave *)v;
248
249     ret = hdb_entry2value (context, entry, &data);
250     if (ret)
251         return ret;
252     ret = krb5_data_realloc (&data, data.length + 4);
253     if (ret) {
254         krb5_data_free (&data);
255         return ret;
256     }
257     memmove ((char *)data.data + 4, data.data, data.length - 4);
258     _krb5_put_int (data.data, ONE_PRINC, 4);
259
260     ret = krb5_write_priv_message (context, slave->ac, &slave->fd, &data);
261     krb5_data_free (&data);
262     return ret;
263 }
264
265 static int
266 send_complete (krb5_context context, slave *s,
267                const char *database, u_int32_t current_version)
268 {
269     krb5_error_code ret;
270     HDB *db;
271     krb5_data data;
272     char buf[8];
273
274     ret = hdb_create (context, &db, database);
275     if (ret)
276         krb5_err (context, 1, ret, "hdb_create: %s", database);
277     ret = db->open (context, db, O_RDONLY, 0);
278     if (ret)
279         krb5_err (context, 1, ret, "db->open");
280
281     _krb5_put_int(buf, TELL_YOU_EVERYTHING, 4);
282
283     data.data   = buf;
284     data.length = 4;
285
286     ret = krb5_write_priv_message(context, s->ac, &s->fd, &data);
287
288     if (ret) {
289         krb5_warn (context, ret, "krb5_write_priv_message");
290         slave_dead(s);
291         return ret;
292     }
293
294     ret = hdb_foreach (context, db, 0, prop_one, s);
295     if (ret) {
296         krb5_warn (context, ret, "hdb_foreach");
297         slave_dead(s);
298         return ret;
299     }
300
301     _krb5_put_int (buf, NOW_YOU_HAVE, 4);
302     _krb5_put_int (buf + 4, current_version, 4);
303     data.length = 8;
304
305     s->version = current_version;
306
307     ret = krb5_write_priv_message(context, s->ac, &s->fd, &data);
308     if (ret) {
309         slave_dead(s);
310         krb5_warn (context, ret, "krb5_write_priv_message");
311         return ret;
312     }
313
314     slave_seen(s);
315
316     return 0;
317 }
318
319 static int
320 send_diffs (krb5_context context, slave *s, int log_fd,
321             const char *database, u_int32_t current_version)
322 {
323     krb5_storage *sp;
324     u_int32_t ver;
325     time_t timestamp;
326     enum kadm_ops op;
327     u_int32_t len;
328     off_t right, left;
329     krb5_data data;
330     int ret = 0;
331
332     if (s->version == current_version)
333         return 0;
334
335     if (s->flags & SLAVE_F_DEAD)
336         return 0;
337
338     sp = kadm5_log_goto_end (log_fd);
339     right = krb5_storage_seek(sp, 0, SEEK_CUR);
340     for (;;) {
341         if (kadm5_log_previous (sp, &ver, &timestamp, &op, &len))
342             abort ();
343         left = krb5_storage_seek(sp, -16, SEEK_CUR);
344         if (ver == s->version)
345             return 0;
346         if (ver == s->version + 1)
347             break;
348         if (left == 0)
349             return send_complete (context, s, database, current_version);
350     }
351     krb5_data_alloc (&data, right - left + 4);
352     krb5_storage_read (sp, (char *)data.data + 4, data.length - 4);
353     krb5_storage_free(sp);
354
355     _krb5_put_int(data.data, FOR_YOU, 4);
356
357     ret = krb5_write_priv_message(context, s->ac, &s->fd, &data);
358     krb5_data_free(&data);
359
360     if (ret) {
361         krb5_warn (context, ret, "krb5_write_priv_message");
362         slave_dead(s);
363         return 1;
364     }
365     slave_seen(s);
366
367     return 0;
368 }
369
370 static int
371 process_msg (krb5_context context, slave *s, int log_fd,
372              const char *database, u_int32_t current_version)
373 {
374     int ret = 0;
375     krb5_data out;
376     krb5_storage *sp;
377     int32_t tmp;
378
379     ret = krb5_read_priv_message(context, s->ac, &s->fd, &out);
380     if(ret) {
381         krb5_warn (context, ret, "error reading message from %s", s->name);
382         return 1;
383     }
384
385     sp = krb5_storage_from_mem (out.data, out.length);
386     krb5_ret_int32 (sp, &tmp);
387     switch (tmp) {
388     case I_HAVE :
389         krb5_ret_int32 (sp, &tmp);
390         s->version = tmp;
391         ret = send_diffs (context, s, log_fd, database, current_version);
392         break;
393     case FOR_YOU :
394     default :
395         krb5_warnx (context, "Ignoring command %d", tmp);
396         break;
397     }
398
399     krb5_data_free (&out);
400
401     slave_seen(s);
402
403     return ret;
404 }
405
406 #define SLAVE_NAME      "Name"
407 #define SLAVE_ADDRESS   "Address"
408 #define SLAVE_VERSION   "Version"
409 #define SLAVE_STATUS    "Status"
410 #define SLAVE_SEEN      "Last Seen"
411
412 static void
413 write_stats(krb5_context context, slave *slaves, u_int32_t current_version)
414 {
415     char str[100];
416     rtbl_t tbl;
417     time_t t = time(NULL);
418     FILE *fp;
419
420     fp = fopen(slave_stats_file, "w");
421     if (fp == NULL)
422         return;
423
424     strftime(str, sizeof(str), "%Y-%m-%d %H:%M:%S",
425              localtime(&t));
426     fprintf(fp, "Status for slaves, last updated: %s\n\n", str);
427
428     fprintf(fp, "Master version: %lu\n\n", (unsigned long)current_version);
429
430     tbl = rtbl_create();
431     if (tbl == NULL) {
432         fclose(fp);
433         return;
434     }
435
436     rtbl_add_column(tbl, SLAVE_NAME, 0);
437     rtbl_add_column(tbl, SLAVE_ADDRESS, 0);
438     rtbl_add_column(tbl, SLAVE_VERSION, RTBL_ALIGN_RIGHT);
439     rtbl_add_column(tbl, SLAVE_STATUS, 0);
440     rtbl_add_column(tbl, SLAVE_SEEN, 0);
441
442     rtbl_set_prefix(tbl, "  ");
443     rtbl_set_column_prefix(tbl, SLAVE_NAME, "");
444
445     while (slaves) {
446         krb5_address addr;
447         krb5_error_code ret;
448         rtbl_add_column_entry(tbl, SLAVE_NAME, slaves->name);
449         ret = krb5_sockaddr2address (context, 
450                                      (struct sockaddr*)&slaves->addr, &addr);
451         if(ret == 0) {
452             krb5_print_address(&addr, str, sizeof(str), NULL);
453             krb5_free_address(context, &addr);
454             rtbl_add_column_entry(tbl, SLAVE_ADDRESS, str);
455         } else
456             rtbl_add_column_entry(tbl, SLAVE_ADDRESS, "<unknown>");
457         
458         snprintf(str, sizeof(str), "%u", (unsigned)slaves->version);
459         rtbl_add_column_entry(tbl, SLAVE_VERSION, str);
460
461         if (slaves->flags & SLAVE_F_DEAD)
462             rtbl_add_column_entry(tbl, SLAVE_STATUS, "Down");
463         else
464             rtbl_add_column_entry(tbl, SLAVE_STATUS, "Up");
465
466         if (strftime(str, sizeof(str), "%Y-%m-%d %H:%M:%S %Z", 
467                      localtime(&slaves->seen)) == 0)
468             strlcpy(str, "Unknown time", sizeof(str));
469         rtbl_add_column_entry(tbl, SLAVE_SEEN, str);
470
471         slaves = slaves->next;
472     }
473
474     rtbl_format(tbl, fp);
475     rtbl_destroy(tbl);
476
477     fclose(fp);
478 }
479
480
481 static char *realm;
482 static int version_flag;
483 static int help_flag;
484 static char *keytab_str = "HDB:";
485 static char *database;
486
487 static struct getargs args[] = {
488     { "realm", 'r', arg_string, &realm },
489     { "keytab", 'k', arg_string, &keytab_str,
490       "keytab to get authentication from", "kspec" },
491     { "database", 'd', arg_string, &database, "database", "file"},
492     { "slave-stats-file", 0, arg_string, &slave_stats_file, "file"},
493     { "version", 0, arg_flag, &version_flag },
494     { "help", 0, arg_flag, &help_flag }
495 };
496 static int num_args = sizeof(args) / sizeof(args[0]);
497
498 int
499 main(int argc, char **argv)
500 {
501     krb5_error_code ret;
502     krb5_context context;
503     void *kadm_handle;
504     kadm5_server_context *server_context;
505     kadm5_config_params conf;
506     int signal_fd, listen_fd;
507     int log_fd;
508     slave *slaves = NULL;
509     u_int32_t current_version, old_version = 0;
510     krb5_keytab keytab;
511     int optind;
512     
513     optind = krb5_program_setup(&context, argc, argv, args, num_args, NULL);
514     
515     if(help_flag)
516         krb5_std_usage(0, args, num_args);
517     if(version_flag) {
518         print_version(NULL);
519         exit(0);
520     }
521
522     pidfile (NULL);
523     krb5_openlog (context, "ipropd-master", &log_facility);
524     krb5_set_warn_dest(context, log_facility);
525
526     ret = krb5_kt_register(context, &hdb_kt_ops);
527     if(ret)
528         krb5_err(context, 1, ret, "krb5_kt_register");
529
530     ret = krb5_kt_resolve(context, keytab_str, &keytab);
531     if(ret)
532         krb5_err(context, 1, ret, "krb5_kt_resolve: %s", keytab_str);
533     
534     memset(&conf, 0, sizeof(conf));
535     if(realm) {
536         conf.mask |= KADM5_CONFIG_REALM;
537         conf.realm = realm;
538     }
539     ret = kadm5_init_with_skey_ctx (context,
540                                     KADM5_ADMIN_SERVICE,
541                                     NULL,
542                                     KADM5_ADMIN_SERVICE,
543                                     &conf, 0, 0, 
544                                     &kadm_handle);
545     if (ret)
546         krb5_err (context, 1, ret, "kadm5_init_with_password_ctx");
547
548     server_context = (kadm5_server_context *)kadm_handle;
549
550     log_fd = open (server_context->log_context.log_file, O_RDONLY, 0);
551     if (log_fd < 0)
552         krb5_err (context, 1, errno, "open %s",
553                   server_context->log_context.log_file);
554
555     signal_fd = make_signal_socket (context);
556     listen_fd = make_listen_socket (context);
557
558     signal (SIGPIPE, SIG_IGN);
559
560     for (;;) {
561         slave *p;
562         fd_set readset;
563         int max_fd = 0;
564         struct timeval to = {30, 0};
565         u_int32_t vers;
566
567         if (signal_fd >= FD_SETSIZE || listen_fd >= FD_SETSIZE)
568             krb5_errx (context, 1, "fd too large");
569
570         FD_ZERO(&readset);
571         FD_SET(signal_fd, &readset);
572         max_fd = max(max_fd, signal_fd);
573         FD_SET(listen_fd, &readset);
574         max_fd = max(max_fd, listen_fd);
575
576         for (p = slaves; p != NULL; p = p->next) {
577             if (p->flags & SLAVE_F_DEAD)
578                 continue;
579             FD_SET(p->fd, &readset);
580             max_fd = max(max_fd, p->fd);
581         }
582
583         ret = select (max_fd + 1,
584                       &readset, NULL, NULL, &to);
585         if (ret < 0) {
586             if (errno == EINTR)
587                 continue;
588             else
589                 krb5_err (context, 1, errno, "select");
590         }
591
592         if (ret == 0) {
593             old_version = current_version;
594             kadm5_log_get_version_fd (log_fd, &current_version);
595
596             if (current_version > old_version)
597                 for (p = slaves; p != NULL; p = p->next) {
598                     if (p->flags & SLAVE_F_DEAD)
599                         continue;
600                     send_diffs (context, p, log_fd, database, current_version);
601                 }
602         }
603
604         if (ret && FD_ISSET(signal_fd, &readset)) {
605             struct sockaddr_un peer_addr;
606             socklen_t peer_len = sizeof(peer_addr);
607
608             if(recvfrom(signal_fd, (void *)&vers, sizeof(vers), 0,
609                         (struct sockaddr *)&peer_addr, &peer_len) < 0) {
610                 krb5_warn (context, errno, "recvfrom");
611                 continue;
612             }
613             --ret;
614             old_version = current_version;
615             kadm5_log_get_version_fd (log_fd, &current_version);
616             for (p = slaves; p != NULL; p = p->next)
617                 send_diffs (context, p, log_fd, database, current_version);
618         }
619
620         for(p = slaves; ret && p != NULL; p = p->next) {
621             if (p->flags & SLAVE_F_DEAD)
622                 continue;
623             if (FD_ISSET(p->fd, &readset)) {
624                 --ret;
625                 if(process_msg (context, p, log_fd, database, current_version))
626                     slave_dead(p);
627             }
628         }
629
630         if (ret && FD_ISSET(listen_fd, &readset)) {
631             add_slave (context, keytab, &slaves, listen_fd);
632             --ret;
633         }
634         write_stats(context, slaves, current_version);
635     }
636
637     return 0;
638 }