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 }
639