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